@mohasinac/appkit 3.5.5 → 3.5.6

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 (69) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +24 -3
  2. package/dist/_internal/client/scaffolds/index.d.ts +0 -2
  3. package/dist/_internal/client/scaffolds/index.js +0 -1
  4. package/dist/client-entry.d.ts +2 -2
  5. package/dist/client-entry.js +2 -2
  6. package/dist/features/account/components/UserSidebar.js +1 -1
  7. package/dist/features/admin/components/AdminFeaturesView.js +1 -1
  8. package/dist/features/admin/components/AdminSidebar.js +1 -1
  9. package/dist/features/admin/components/DataTable.js +1 -1
  10. package/dist/features/admin/components/index.d.ts +0 -2
  11. package/dist/features/admin/components/index.js +0 -1
  12. package/dist/features/auctions/components/MarketplaceAuctionCard.js +1 -1
  13. package/dist/features/before-after/components/BeforeAfterSlider.js +4 -0
  14. package/dist/features/blog/components/BlogFeaturedCard.js +1 -1
  15. package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -9
  16. package/dist/features/events/components/EventCard.js +1 -1
  17. package/dist/features/events/components/EventRafflesSection.d.ts +1 -1
  18. package/dist/features/events/components/EventRafflesSection.js +3 -3
  19. package/dist/features/homepage/components/BeforeAfterCard.js +5 -0
  20. package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +1 -1
  21. package/dist/features/homepage/components/FeaturedBundlesSection.js +3 -1
  22. package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
  23. package/dist/features/homepage/components/HeroCarousel.js +1 -1
  24. package/dist/features/layout/NavbarLayout.js +1 -1
  25. package/dist/features/layout/TitleBarLayout.js +1 -1
  26. package/dist/features/media/MediaLightbox.js +7 -1
  27. package/dist/features/media/components/MediaSlider.js +4 -1
  28. package/dist/features/media/modals/ImageCropModal.js +1 -1
  29. package/dist/features/media/upload/ImageUpload.js +24 -12
  30. package/dist/features/orders/components/MarketplaceOrderCard.js +1 -1
  31. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  32. package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
  33. package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
  34. package/dist/features/products/components/PrizeDrawsSection.d.ts +1 -1
  35. package/dist/features/products/components/PrizeDrawsSection.js +3 -2
  36. package/dist/features/products/components/ProductForm.js +4 -9
  37. package/dist/features/products/components/ProductGrid.js +1 -1
  38. package/dist/features/promotions/components/CouponCard.js +1 -1
  39. package/dist/features/seller/components/SellerProductShell.js +4 -4
  40. package/dist/features/seller/components/SellerSidebar.js +1 -1
  41. package/dist/features/shipments/components/ShipmentItemLinkModal.js +5 -5
  42. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  43. package/dist/index.d.ts +0 -3
  44. package/dist/index.js +0 -3
  45. package/dist/react/hooks/useClickOutside.js +3 -0
  46. package/dist/seed/site-settings-seed-data.js +9 -2
  47. package/dist/server-entry.d.ts +2 -2
  48. package/dist/server-entry.js +1 -1
  49. package/dist/styles.css +11 -4
  50. package/dist/tailwind-utilities.css +1 -1
  51. package/dist/tokens/themes/cobalt-night.d.ts +17 -0
  52. package/dist/tokens/themes/cobalt-night.js +50 -0
  53. package/dist/tokens/themes/index.d.ts +9 -1
  54. package/dist/tokens/themes/index.js +12 -1
  55. package/dist/tokens/themes/sunset.d.ts +18 -0
  56. package/dist/tokens/themes/sunset.js +51 -0
  57. package/dist/tokens/tokens.css +10 -3
  58. package/dist/ui/components/BaseListingCard.d.ts +2 -1
  59. package/dist/ui/components/BaseListingCard.js +4 -4
  60. package/dist/ui/components/Card.d.ts +1 -1
  61. package/dist/ui/components/Card.js +2 -2
  62. package/dist/ui/components/Div.js +2 -2
  63. package/dist/ui/components/Form.d.ts +1 -1
  64. package/dist/ui/components/Form.js +2 -2
  65. package/dist/ui/components/Input.js +1 -0
  66. package/dist/ui/components/ListingToolbar.js +1 -1
  67. package/dist/ui/components/QuickCreateModal.style.css +1 -1
  68. package/dist/ui/components/Slider.js +5 -1
  69. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../../errors/normalize";
4
4
  /**
5
5
  * DashboardLayoutClient — unified client island that replaces the ~75 lines of
@@ -23,6 +23,7 @@ import { normalizeError } from "../../../../errors/normalize";
23
23
  */
24
24
  import { useCallback, useEffect, useState, startTransition } from "react";
25
25
  import { usePathname } from "next/navigation";
26
+ import Link from "next/link";
26
27
  import { useDashboardNav } from "../../../../features/layout/DashboardNavContext";
27
28
  import { AdminSidebar } from "../../../../features/admin/components/AdminSidebar";
28
29
  import { StoreSidebar } from "../../../../features/seller/components/SellerSidebar";
@@ -31,7 +32,8 @@ import { DASHBOARD_DESKTOP_MEDIA_QUERY } from "../../../shared/features/layout/c
31
32
  import { filterNavItems } from "./filterNavItems";
32
33
  import { useSiteSettings } from "../../../../core/hooks/useSiteSettings";
33
34
  import { useTheme } from "../../theme";
34
- import { BackgroundRenderer, Div } from "../../../../ui";
35
+ import { BackgroundRenderer, Div, Nav, Span } from "../../../../ui";
36
+ import { useVisualViewportInset } from "../../../../react/hooks/useVisualViewportInset";
35
37
  /**
36
38
  * Hoisted drawer-state hook — the matchMedia-aware open/close logic that was
37
39
  * triplicated across admin/store/user layouts. Used internally by
@@ -125,6 +127,24 @@ function filterGroups(groups, navConfig, permissions) {
125
127
  }))
126
128
  .filter((group) => group.items.length > 0);
127
129
  }
130
+ const MAX_BOTTOM_NAV_ITEMS = 5;
131
+ /**
132
+ * Mobile bottom tab bar (<lg) for the dashboard shells. Admin/store/user
133
+ * previously had no bottom-nav — this was the source of "hard to navigate
134
+ * on mobile" (the sidebar's only mobile affordance was an off-canvas drawer
135
+ * behind a hamburger toggle). Pattern lifted from the unused
136
+ * `DashboardScaffold` scaffold, which has since been retired in favour of
137
+ * wiring it directly here where the sidebars already live.
138
+ */
139
+ function DashboardBottomNav({ items, activeHref }) {
140
+ const isKeyboardOpen = useVisualViewportInset();
141
+ if (items.length === 0)
142
+ return null;
143
+ return (_jsx(Nav, { "aria-label": "Dashboard bottom navigation", "aria-hidden": isKeyboardOpen, className: `fixed bottom-0 left-0 right-0 z-[var(--appkit-z-bottom-nav)] flex items-stretch justify-around border-t border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] lg:hidden transition-transform duration-200 ease-out ${isKeyboardOpen ? "translate-y-full pointer-events-none" : "translate-y-0"}`, children: items.slice(0, MAX_BOTTOM_NAV_ITEMS).map((item) => {
144
+ const isActive = activeHref === item.href || activeHref.startsWith(`${item.href}/`);
145
+ return (_jsxs(Link, { href: item.href, className: `flex flex-1 flex-col items-center justify-center gap-[var(--appkit-space-0-5)] py-[var(--appkit-space-2)] ${isActive ? "text-[var(--appkit-color-primary)]" : "text-[var(--appkit-color-text-muted)]"}`, "aria-current": isActive ? "page" : undefined, children: [item.icon && _jsx(Span, { size: "base", children: item.icon }), _jsx(Span, { size: "xs", className: "max-w-full truncate", children: item.label })] }, item.href));
146
+ }) }));
147
+ }
128
148
  const DEFAULT_CONTENT_PADDING = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pl-14 lg:pr-6 xl:pl-16 xl:pr-10";
129
149
  const DEFAULT_CONTENT_MAX_WIDTH = "max-w-screen-2xl";
130
150
  export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
@@ -147,7 +167,8 @@ export function DashboardLayoutClient({ variant, groups, permissions, activeHref
147
167
  : groups;
148
168
  return (_jsxs(_Fragment, { children: [hasBackground && (_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: resolvedLightBackground ?? { type: "color", value: "" }, darkMode: resolvedDarkBackground ?? { type: "color", value: "" } })), variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: adminGroups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: filteredGroups.flatMap((g) => g.items), groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className })), _jsx(Div, { className: [
149
169
  "w-full flex-1 flex flex-col min-h-[calc(100dvh-var(--header-height,3.5rem))]",
170
+ "pb-[var(--appkit-space-16)] lg:pb-[0]", // clears the fixed mobile bottom-nav
150
171
  contentPadding ?? DEFAULT_CONTENT_PADDING,
151
172
  contentSurface,
152
- ].filter(Boolean).join(" "), children: _jsx(Div, { className: ["w-full flex-1 mx-auto", contentMaxWidth ?? DEFAULT_CONTENT_MAX_WIDTH].filter(Boolean).join(" "), children: children }) })] }));
173
+ ].filter(Boolean).join(" "), children: _jsx(Div, { className: ["w-full flex-1 mx-auto", contentMaxWidth ?? DEFAULT_CONTENT_MAX_WIDTH].filter(Boolean).join(" "), children: children }) }), _jsx(DashboardBottomNav, { items: filteredGroups.flatMap((g) => g.items), activeHref: activeHref })] }));
153
174
  }
@@ -1,4 +1,2 @@
1
1
  export { AppShell } from "./AppShell";
2
2
  export type { AppShellProps, AppShellRenderContext } from "./AppShell";
3
- export { DashboardScaffold } from "./DashboardScaffold";
4
- export type { DashboardScaffoldProps, DashboardScaffoldRenderContext, } from "./DashboardScaffold";
@@ -1,2 +1 @@
1
1
  export { AppShell } from "./AppShell";
2
- export { DashboardScaffold } from "./DashboardScaffold";
@@ -15,8 +15,8 @@
15
15
  export * from "./index";
16
16
  export { LabelsProvider, useLabels } from "./_internal/client/i18n/LabelsProvider";
17
17
  export type { AppkitLabelSet } from "./_internal/client/i18n/LabelsProvider";
18
- export { AppShell, DashboardScaffold } from "./_internal/client/scaffolds/index";
19
- export type { AppShellProps, AppShellRenderContext, DashboardScaffoldProps, DashboardScaffoldRenderContext, } from "./_internal/client/scaffolds/index";
18
+ export { AppShell } from "./_internal/client/scaffolds/index";
19
+ export type { AppShellProps, AppShellRenderContext, } from "./_internal/client/scaffolds/index";
20
20
  export { toClient, clientInitial } from "./_internal/shared/serialization/index";
21
21
  export type { AppkitConfig, AppkitFirebaseConfig, AppkitFirebaseExtensions, AppkitVercelConfig, AppkitBrandConfig, AppkitSeoConfig, AppkitI18nConfig, FirestoreIndex, FirestoreIndexField, FirestoreFieldOverride, } from "./_internal/shared/config/schema";
22
22
  export { SEMANTIC_COLORS, SEMANTIC_COLORS_DARK, SEMANTIC_RADIUS, SEMANTIC_SHADOWS, SEMANTIC_Z_INDEX, MOTION_TOKENS, BREAKPOINTS, PLATFORM_LIMITS, } from "./_internal/shared/tokens/index";
@@ -20,8 +20,8 @@
20
20
  export * from "./index";
21
21
  // New i18n contract — available to consumers immediately
22
22
  export { LabelsProvider, useLabels } from "./_internal/client/i18n/LabelsProvider";
23
- // Scaffolds — slot-driven shell + dashboard chrome (S6)
24
- export { AppShell, DashboardScaffold } from "./_internal/client/scaffolds/index";
23
+ // Scaffolds — slot-driven shell chrome (S6)
24
+ export { AppShell } from "./_internal/client/scaffolds/index";
25
25
  // Hydration helpers (CC-3) — safe to call from RSC and client
26
26
  export { toClient, clientInitial } from "./_internal/shared/serialization/index";
27
27
  export { SEMANTIC_COLORS, SEMANTIC_COLORS_DARK, SEMANTIC_RADIUS, SEMANTIC_SHADOWS, SEMANTIC_Z_INDEX, MOTION_TOKENS, BREAKPOINTS, PLATFORM_LIMITS, } from "./_internal/shared/tokens/index";
@@ -27,7 +27,7 @@ function NavLink({ item, isActive, onClick }) {
27
27
  const linkClass = `flex items-center 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
28
28
  ? "bg-primary-50 dark:bg-primary-900/25 text-primary-700 dark:text-primary-300"
29
29
  : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-900 dark:hover:text-zinc-100"}`;
30
- return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-70", children: item.icon }), _jsx(Span, { align: "end", className: "flex-1 truncate", children: item.label })] }), showConfirm && item.confirm && (_jsx(ConfirmDeleteModal, { isOpen: true, title: item.confirm.title ?? "Confirm", message: item.confirm.message, confirmText: "Continue", variant: "warning", onConfirm: () => {
30
+ return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-70", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label })] }), showConfirm && item.confirm && (_jsx(ConfirmDeleteModal, { isOpen: true, title: item.confirm.title ?? "Confirm", message: item.confirm.message, confirmText: "Continue", variant: "warning", onConfirm: () => {
31
31
  setShowConfirm(false);
32
32
  onClick?.();
33
33
  window.location.href = item.href;
@@ -12,7 +12,7 @@ import { AdminFeatureEditorView } from "./AdminFeatureEditorView";
12
12
  import { PRODUCT_FEATURE_SCOPE_TABS } from "../../products/constants/product-features.constants";
13
13
  const PAGE_SIZE = 50;
14
14
  const DEFAULT_SCOPE = "platform";
15
- const STICKY_TABS_CLASS = "sticky top-[calc(var(--header-height,0px)+44px)] z-10 bg-white/95 bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]";
15
+ const STICKY_TABS_CLASS = "sticky top-[calc(var(--header-height,0px)+44px)] z-10 bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]";
16
16
  function mapFeatureRow(item, index) {
17
17
  const productTypes = Array.isArray(item.productTypes) ? item.productTypes : [];
18
18
  const isActive = typeof item.isActive === "boolean" ? item.isActive : true;
@@ -16,7 +16,7 @@ function isNavItemActive(item, activePath) {
16
16
  function NavLink({ item, isActive, onClick }) {
17
17
  return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center 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
18
18
  ? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"
19
- : "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, { align: "end", className: "flex-1 truncate", children: item.label })] }));
19
+ : "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: "flex-1 truncate", children: item.label })] }));
20
20
  }
21
21
  function GroupsContent({ groups, activePath, onItemClick, }) {
22
22
  const [openGroups, setOpenGroups] = useState(() => Object.fromEntries(groups.map((g) => [
@@ -39,7 +39,7 @@ function SelectableRow({ row, columns, isSelected, onToggle, renderRowActions, o
39
39
  }
40
40
  : undefined;
41
41
  const isInteractive = Boolean(onRowClick ?? rowHref);
42
- return (_jsxs(Tr, { "data-testid": "data-table-row", onClick: handleClick, onKeyDown: handleKeyDown, role: isInteractive ? "link" : undefined, tabIndex: isInteractive ? 0 : undefined, onMouseDown: selectionEnabled && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: selectionEnabled && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: selectionEnabled && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: selectionEnabled && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: selectionEnabled && !isSelected ? longPress.onTouchEnd : undefined, className: `hover:bg-[var(--appkit-color-bg)] dark:hover:bg-[var(--appkit-color-bg)] ${isInteractive ? "cursor-pointer" : ""} ${isSelected ? "bg-primary/5 dark:bg-primary/10" : ""}`, border: "default", children: [selectionEnabled && (_jsx(Td, { padding: "xs-3", className: "relative w-10", onClick: (e) => e.stopPropagation(), children: _jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onToggle?.(row.id, !isSelected); }, label: isSelected ? "Deselect row" : "Select row", position: "top-1/2 left-2 -translate-y-1/2", "data-testid": "row-checkbox" }) })), columns.map((col) => (_jsx(Td, { color: "muted", className: col.className ?? "", padding: "md", children: col.render
42
+ return (_jsxs(Tr, { "data-testid": "data-table-row", onClick: handleClick, onKeyDown: handleKeyDown, role: isInteractive ? "link" : undefined, tabIndex: isInteractive ? 0 : undefined, onMouseDown: selectionEnabled && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: selectionEnabled && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: selectionEnabled && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: selectionEnabled && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: selectionEnabled && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: selectionEnabled && !isSelected ? longPress.onTouchCancel : undefined, className: `hover:bg-[var(--appkit-color-bg)] dark:hover:bg-[var(--appkit-color-bg)] ${isInteractive ? "cursor-pointer" : ""} ${isSelected ? "bg-primary/5 dark:bg-primary/10" : ""}`, border: "default", children: [selectionEnabled && (_jsx(Td, { padding: "xs-3", className: "relative w-10", onClick: (e) => e.stopPropagation(), children: _jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onToggle?.(row.id, !isSelected); }, label: isSelected ? "Deselect row" : "Select row", position: "top-1/2 left-2 -translate-y-1/2", "data-testid": "row-checkbox" }) })), columns.map((col) => (_jsx(Td, { color: "muted", className: col.className ?? "", padding: "md", children: col.render
43
43
  ? col.render(row)
44
44
  : String(row[col.key] ?? "") }, col.key))), renderRowActions && (_jsx(Td, { padding: "xs-3", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] }));
45
45
  }
@@ -109,8 +109,6 @@ export { AdminCarouselEditorView } from "./AdminCarouselEditorView";
109
109
  export type { AdminCarouselEditorViewProps } from "./AdminCarouselEditorView";
110
110
  export { AdminDashboardView } from "./AdminDashboardView";
111
111
  export type { AdminDashboardViewProps } from "./AdminDashboardView";
112
- export { QuickActionsPanel } from "./QuickActionsPanel";
113
- export type { QuickActionItem, QuickActionsPanelProps, } from "./QuickActionsPanel";
114
112
  export { AdminSiteView } from "./AdminSiteView";
115
113
  export type { AdminSiteViewProps } from "./AdminSiteView";
116
114
  export { AdminSiteSettingsView } from "./AdminSiteSettingsView";
@@ -59,7 +59,6 @@ export { AdminAdEditorView } from "./AdminAdEditorView";
59
59
  export { AdminCarouselView } from "./AdminCarouselView";
60
60
  export { AdminCarouselEditorView } from "./AdminCarouselEditorView";
61
61
  export { AdminDashboardView } from "./AdminDashboardView";
62
- export { QuickActionsPanel } from "./QuickActionsPanel";
63
62
  export { AdminSiteView } from "./AdminSiteView";
64
63
  export { AdminSiteSettingsView } from "./AdminSiteSettingsView";
65
64
  export { AdminAnalyticsView } from "./AdminAnalyticsView";
@@ -161,5 +161,5 @@ export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", v
161
161
  remaining, resolvedBuyoutPrice, inWishlist, wishlistLoading, wishlistActions,
162
162
  countdownClass, mergedLabels, handleWishlist, handleNavigate,
163
163
  };
164
- return (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
164
+ return (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
165
165
  }
@@ -10,6 +10,10 @@ export function BeforeAfterSlider({ item, className = "", }) {
10
10
  return;
11
11
  move(e.clientX);
12
12
  }
13
+ // audit-touch-handler-ok: position is derived fresh from each move event's
14
+ // coordinates, no "dragging in progress" flag or timer persists across
15
+ // gestures — a cancelled touch just leaves `position` at its last value,
16
+ // which is the same outcome as the user simply stopping mid-drag.
13
17
  function handleTouchMove(e) {
14
18
  move(e.touches[0].clientX);
15
19
  }
@@ -31,5 +31,5 @@ export function BlogFeaturedCard({ post, href, labels = {}, className = "", sele
31
31
  day: "numeric",
32
32
  })
33
33
  : "";
34
- return (_jsxs(Article, { rounded: "xl", shadow: "hover-md", className: `group relative h-full overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${isSelected ? "ring-2 ring-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, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(TextLink, { href: href, layout: "flex-col", className: "h-full", children: [_jsx(Div, { className: `relative aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(MediaImage, { src: coverImageUrl, alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, surface: "muted", className: "h-full w-full" })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { gap: "xs", className: "mb-2", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block capitalize ${CATEGORY_BADGE[post.category] ?? "bg-[var(--appkit-color-bg)] text-[var(--appkit-color-text-muted)]"}`, rounded: "full", padding: "pill-xs", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `leading-snug mb-1`, color: "primary", truncate: 2, size: "base", weight: "semibold", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `mb-3`, color: "muted", truncate: 2, size: "sm", children: post.excerpt })), _jsxs(Row, { color: "muted", textSize: "xs", className: "mt-auto", gap: "3", wrap: true, children: [post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
34
+ return (_jsxs(Article, { rounded: "xl", shadow: "hover-md", className: `group relative h-full overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${isSelected ? "ring-2 ring-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(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(TextLink, { href: href, layout: "flex-col", className: "h-full", children: [_jsx(Div, { className: `relative aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(MediaImage, { src: coverImageUrl, alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, surface: "muted", className: "h-full w-full" })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { gap: "xs", className: "mb-2", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block capitalize ${CATEGORY_BADGE[post.category] ?? "bg-[var(--appkit-color-bg)] text-[var(--appkit-color-text-muted)]"}`, rounded: "full", padding: "pill-xs", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `leading-snug mb-1`, color: "primary", truncate: 2, size: "base", weight: "semibold", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `mb-3`, color: "muted", truncate: 2, size: "sm", children: post.excerpt })), _jsxs(Row, { color: "muted", textSize: "xs", className: "mt-auto", gap: "3", wrap: true, children: [post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
35
35
  }
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  * `bundleQueryResolvedAt` timestamp. This editor only writes the rule.
15
15
  */
16
16
  import { useCallback } from "react";
17
- import { Div, Input, Row, Select, Stack, Text } from "../../../ui";
17
+ import { Div, Input, Row, Select, Stack, TagInput, Text } from "../../../ui";
18
18
  import { CategoryInlineSelect } from "../../seller/components/CategoryInlineSelect";
19
19
  import { BrandInlineSelect } from "../../seller/components/BrandInlineSelect";
20
20
  import { BUNDLE_MAX_ITEMS } from "../../../_internal/shared/features/categories/bundle-config";
@@ -40,14 +40,7 @@ export function BundleDynamicRuleEditor({ value, onChange, disabled = false, })
40
40
  const rule = value ?? DEFAULT_DYNAMIC_RULE;
41
41
  const patch = useCallback((next) => onChange({ ...rule, ...next }), [rule, onChange]);
42
42
  const patchFilter = useCallback((next) => onChange({ ...rule, filter: { ...rule.filter, ...next } }), [rule, onChange]);
43
- const tagsValue = (rule.filter.tags ?? []).join(", ");
44
- return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.title }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.dynamic.hint })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.categorySlug }), _jsx(CategoryInlineSelect, { value: rule.filter.categorySlug ?? "", onChange: (id) => patchFilter({ categorySlug: id || undefined }), disabled: disabled, placeholder: "Filter by category\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.brandSlug }), _jsx(BrandInlineSelect, { value: rule.filter.brandSlug ?? "", onChange: (id) => patchFilter({ brandSlug: id || undefined }), disabled: disabled, placeholder: "Filter by brand\u2026" })] })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.tags }), _jsx(Input, { type: "text", value: tagsValue, onChange: (e) => {
45
- const parsed = e.target.value
46
- .split(",")
47
- .map((t) => t.trim())
48
- .filter(Boolean);
49
- patchFilter({ tags: parsed.length ? parsed : undefined });
50
- }, placeholder: "pokemon, starter-deck", disabled: disabled })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-3", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.listingType }), _jsx(Select, { options: LISTING_TYPE_OPTIONS, value: rule.filter.listingType ?? "", onValueChange: (next) => patchFilter({
43
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.title }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.dynamic.hint })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.categorySlug }), _jsx(CategoryInlineSelect, { value: rule.filter.categorySlug ?? "", onChange: (id) => patchFilter({ categorySlug: id || undefined }), disabled: disabled, placeholder: "Filter by category\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.brandSlug }), _jsx(BrandInlineSelect, { value: rule.filter.brandSlug ?? "", onChange: (id) => patchFilter({ brandSlug: id || undefined }), disabled: disabled, placeholder: "Filter by brand\u2026" })] })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.tags }), _jsx(TagInput, { value: rule.filter.tags ?? [], onChange: (tags) => patchFilter({ tags: tags.length ? tags : undefined }), placeholder: "pokemon, starter-deck", disabled: disabled })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-3", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.listingType }), _jsx(Select, { options: LISTING_TYPE_OPTIONS, value: rule.filter.listingType ?? "", onValueChange: (next) => patchFilter({
51
44
  listingType: next === "" ? undefined : next,
52
45
  }), disabled: disabled, "aria-label": BUNDLE_COPY.adminEditor.dynamic.fields.listingType })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.orderBy }), _jsx(Select, { options: ORDER_BY_OPTIONS.map((o) => ({
53
46
  label: o.label,
@@ -33,6 +33,6 @@ export function EventCard({ event, labels = {}, onParticipate, className = "", s
33
33
  const msLeft = endsAt.getTime() - now.getTime();
34
34
  const daysLeft = Math.max(0, Math.ceil(msLeft / (1000 * 60 * 60 * 24)));
35
35
  const detailHref = String(ROUTES.PUBLIC.EVENT_DETAIL(event.slug ?? event.id));
36
- return (_jsxs(Article, { border: "default", rounded: "xl", shadow: "hover-md", className: `group relative h-full ${LAYOUT.cardHeight.event} 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, 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 ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: event.coverImageUrl, 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
+ return (_jsxs(Article, { border: "default", rounded: "xl", shadow: "hover-md", className: `group relative h-full ${LAYOUT.cardHeight.event} 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 ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: event.coverImageUrl, 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 &&
37
37
  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"] }))] })] }));
38
38
  }
@@ -6,4 +6,4 @@ export interface EventRafflesSectionProps {
6
6
  * Renders a live-raffle/spin-wheel strip on the homepage.
7
7
  * W1-38 (2026-05-23): fetches events with hasRaffle=true and renders the EventCard grid.
8
8
  */
9
- export declare function EventRafflesSection({ config, }: EventRafflesSectionProps): Promise<import("react").JSX.Element>;
9
+ export declare function EventRafflesSection({ config, }: EventRafflesSectionProps): Promise<import("react").JSX.Element | null>;
@@ -2,9 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
3
  import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
- import Link from "next/link";
6
5
  import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
7
- import { ROUTES } from "../../../next";
8
6
  import { eventRepository } from "../repository/events.repository";
9
7
  import { EventCard } from "./EventCard";
10
8
  /**
@@ -28,5 +26,7 @@ export async function EventRafflesSection({ config, }) {
28
26
  void normalizeError(_err);
29
27
  events = [];
30
28
  }
31
- return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), events.length === 0 ? (_jsxs(Stack, { align: "start", gap: "sm", className: "border-dashed text-left", border: "strong", paddingY: "y-3xl", paddingX: "x-lg", rounded: "2xl", children: [_jsx(Text, { size: "sm", color: "muted", children: "No upcoming raffles \u2014 keep an eye on the events page." }), _jsx(Link, { href: String(ROUTES.PUBLIC.EVENTS), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: "Browse all events \u2192" })] })) : (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) }))] }) }) }));
29
+ if (events.length === 0)
30
+ return null;
31
+ return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), _jsx(Div, { gap: "3", className: "fluid-grid-card", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })] }) }) }));
32
32
  }
@@ -45,11 +45,16 @@ export function BeforeAfterCard({ item, beforeLabel = "Before", afterLabel = "Af
45
45
  window.addEventListener("mouseup", onUp);
46
46
  window.addEventListener("touchmove", onMove, { passive: false });
47
47
  window.addEventListener("touchend", onUp);
48
+ // Without this, an interrupted touch (OS/browser gesture takeover)
49
+ // never fires touchend — `dragging` stays true forever, leaking these
50
+ // window-level listeners for the rest of the component's lifetime.
51
+ window.addEventListener("touchcancel", onUp);
48
52
  return () => {
49
53
  window.removeEventListener("mousemove", onMove);
50
54
  window.removeEventListener("mouseup", onUp);
51
55
  window.removeEventListener("touchmove", onMove);
52
56
  window.removeEventListener("touchend", onUp);
57
+ window.removeEventListener("touchcancel", onUp);
53
58
  };
54
59
  }, [dragging, updatePosition]);
55
60
  return (_jsxs(Div, { className: `border-border ${__O.hidden} border bg-card`, rounded: "2xl", shadow: "sm", children: [_jsxs(Div, { ref: containerRef, className: "relative aspect-[4/3] cursor-col-resize overflow-hidden select-none", onMouseDown: (e) => {
@@ -17,4 +17,4 @@ export interface FeaturedBundlesSectionProps {
17
17
  bundleSlug: string;
18
18
  }) => Promise<unknown>;
19
19
  }
20
- export declare function FeaturedBundlesSection({ title, description, initialItems, className, onBuyNow, }: FeaturedBundlesSectionProps): React.JSX.Element;
20
+ export declare function FeaturedBundlesSection({ title, description, initialItems, className, onBuyNow, }: FeaturedBundlesSectionProps): React.JSX.Element | null;
@@ -15,7 +15,9 @@ const __O = {
15
15
  const PLACEHOLDER_EMOJI = "📦";
16
16
  export function FeaturedBundlesSection({ title = BUNDLE_COPY.featuredDefaultTitle, description, initialItems = [], className = "", onBuyNow, }) {
17
17
  const items = initialItems.filter((c) => c.categoryType === "bundle");
18
- return (_jsx(Section, { className: `${className}`, padding: "y-2xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLES), className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: BUNDLE_COPY.featured.viewAll })] }), items.length === 0 ? (_jsx(Div, { className: "border-dashed text-left", padding: "y-3xl", rounded: "xl", border: "default", children: _jsx(Text, { color: "muted", children: BUNDLE_COPY.featured.empty }) })) : (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: items.map((bundle) => (_jsx(FeaturedBundleCard, { bundle: bundle, onBuyNow: onBuyNow }, bundle.id))) }))] }) }));
18
+ if (items.length === 0)
19
+ return null;
20
+ return (_jsx(Section, { className: `${className}`, padding: "y-2xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLES), className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: BUNDLE_COPY.featured.viewAll })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: items.map((bundle) => (_jsx(FeaturedBundleCard, { bundle: bundle, onBuyNow: onBuyNow }, bundle.id))) })] }) }));
19
21
  }
20
22
  function FeaturedBundleCard({ bundle, onBuyNow }) {
21
23
  const stock = bundle.bundleStockStatus ?? "in_stock";
@@ -31,10 +31,10 @@ function ReviewCard({ review, showRating, showDate, }) {
31
31
  }
32
32
  // --- Empty / Not Configured States -------------------------------------------
33
33
  function NotConfiguredState() {
34
- return (_jsxs(Stack, { textSize: "sm", justify: "center", color: "faint", padding: "y-4xl", align: "start", children: [_jsxs("svg", { className: "w-10 h-10 mb-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("path", { d: "M12 8v4m0 4h.01" })] }), "Google Reviews not configured. Add your Google Maps API key in Site Settings."] }));
34
+ return (_jsxs(Stack, { textSize: "sm", justify: "center", color: "faint", padding: "y-4xl", align: "center", children: [_jsxs("svg", { className: "w-10 h-10 mb-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("path", { d: "M12 8v4m0 4h.01" })] }), "Google Reviews not configured. Add your Google Maps API key in Site Settings."] }));
35
35
  }
36
36
  function EmptyState() {
37
- return (_jsxs(Stack, { textSize: "sm", justify: "center", color: "faint", padding: "y-4xl", align: "start", children: [_jsx("svg", { className: "w-10 h-10 mb-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: _jsx("path", { d: "M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" }) }), "No reviews yet."] }));
37
+ return (_jsxs(Stack, { textSize: "sm", justify: "center", color: "faint", padding: "y-4xl", align: "center", children: [_jsx("svg", { className: "w-10 h-10 mb-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: _jsx("path", { d: "M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" }) }), "No reviews yet."] }));
38
38
  }
39
39
  export async function GoogleReviewsSection(config) {
40
40
  const { placeId: configPlaceId, maxReviews = 6, minRating = 0, layout = "grid", showRating = true, showDate = true, linkToGoogleMaps = true, googleMapsUrl, } = config;
@@ -177,7 +177,7 @@ export function HeroCarousel({ initialSlides, push } = {}) {
177
177
  }
178
178
  };
179
179
  if (isLoading) {
180
- return (_jsx(Div, { className: `relative w-full ${heightClass} ${HERO_PLACEHOLDER_BG} animate-pulse`, children: _jsx(Div, { layout: "flex", align: "center", justify: "start", className: `${POSITION_FILL} p-[var(--appkit-space-4)]`, children: _jsx(Text, { variant: "secondary", children: "Loading..." }) }) }));
180
+ return (_jsx(Div, { className: `relative w-full ${heightClass} ${HERO_PLACEHOLDER_BG} animate-pulse`, children: _jsx(Div, { layout: "flex", align: "center", justify: "center", className: `${POSITION_FILL} p-[var(--appkit-space-4)]`, children: _jsx(Text, { variant: "secondary", children: "Loading..." }) }) }));
181
181
  }
182
182
  if (!slides || slides.length === 0) {
183
183
  return (_jsxs(Div, { gap: "4", layout: "flex-col", align: "start", justify: "center", className: "relative w-full min-h-[260px] md:min-h-[60vh] bg-[image:var(--appkit-gradient-section-cool)] px-[var(--appkit-space-6)]", children: [_jsx(Heading, { level: 2, className: "text-primary", size: "2xl", weight: "bold", align: "start", children: "Coming Soon" }), _jsx(Text, { variant: "secondary", className: "max-w-md", align: "start", children: "Amazing deals are on their way. Stay tuned!" })] }));
@@ -51,5 +51,5 @@ export function NavbarLayout({ items, activeHref, id = "main-navbar", ariaLabel
51
51
  if (inline) {
52
52
  return (_jsx(Ul, { "aria-label": ariaLabel, className: "hidden xl:flex items-center justify-end gap-[var(--appkit-space-0-5)] xl:gap-[var(--appkit-space-1)] w-full", children: items.map((item) => (_jsx(Li, { children: renderItem ? (renderItem(item, activeHref === item.href)) : (_jsx(DefaultNavItem, { item: item, isActive: activeHref === item.href })) }, item.href))) }));
53
53
  }
54
- return (_jsx(Nav, { border: "subtle", id: id, "aria-label": ariaLabel, className: "hidden lg:block bg-white/95 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b", children: _jsxs(Row, { className: "container mx-auto sm:px-[var(--appkit-space-6)] lg:px-[var(--appkit-space-8)] max-w-[1920px] h-10", padding: "x-md", align: "center", children: [_jsxs(Div, { className: `relative flex-1 ${__O.hidden}`, children: [canScrollLeft && (_jsx(NavbarChevron, { direction: "left", onClick: () => scroll("left") })), _jsx(Ul, { ref: scrollRef, className: "flex items-center justify-end gap-[var(--appkit-space-0-5)] lg:gap-[var(--appkit-space-1)] overflow-x-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]", children: items.map((item) => (_jsx(Li, { className: "shrink-0", children: renderItem ? (renderItem(item, activeHref === item.href)) : (_jsx(DefaultNavItem, { item: item, isActive: activeHref === item.href })) }, item.href))) }), canScrollRight && (_jsx(NavbarChevron, { direction: "right", onClick: () => scroll("right") }))] }), rightSlot && _jsx(Div, { className: "shrink-0 ml-2", children: rightSlot })] }) }));
54
+ return (_jsx(Nav, { border: "subtle", id: id, "aria-label": ariaLabel, className: "hidden lg:block bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b", children: _jsxs(Row, { className: "container mx-auto sm:px-[var(--appkit-space-6)] lg:px-[var(--appkit-space-8)] max-w-[1920px] h-10", padding: "x-md", align: "center", children: [_jsxs(Div, { className: `relative flex-1 ${__O.hidden}`, children: [canScrollLeft && (_jsx(NavbarChevron, { direction: "left", onClick: () => scroll("left") })), _jsx(Ul, { ref: scrollRef, className: "flex items-center justify-end gap-[var(--appkit-space-0-5)] lg:gap-[var(--appkit-space-1)] overflow-x-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]", children: items.map((item) => (_jsx(Li, { className: "shrink-0", children: renderItem ? (renderItem(item, activeHref === item.href)) : (_jsx(DefaultNavItem, { item: item, isActive: activeHref === item.href })) }, item.href))) }), canScrollRight && (_jsx(NavbarChevron, { direction: "right", onClick: () => scroll("right") }))] }), rightSlot && _jsx(Div, { className: "shrink-0 ml-2", children: rightSlot })] }) }));
55
55
  }
@@ -33,7 +33,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
33
33
  const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
34
34
  const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
35
35
  // ── Render ───────────────────────────────────────────────────────────────────
36
- return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-zinc-100 border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, size: "md" }) }) }), siteLogoUrl ? (_jsx(Row, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, size: "md" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex ml-auto", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
36
+ return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, size: "md" }) }) }), siteLogoUrl ? (_jsx(Row, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, size: "md" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex ml-auto", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
37
37
  ? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
38
38
  : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, tourBtn, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { border: "subtle", as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t px-[var(--appkit-space-1)]", children: [notificationsEl, wishlistEl, cartEl, profileEl] }))] })] }));
39
39
  }
@@ -220,10 +220,16 @@ export function MediaLightbox({ items, initialIndex = 0, isOpen, onClose, labels
220
220
  }
221
221
  touchState.current = {};
222
222
  }, [zoom, goNext, goPrev]);
223
+ // An interrupted gesture (OS/browser takeover) never fires touchend —
224
+ // without this, stale touchState.current values corrupt the math for the
225
+ // next unrelated pinch/swipe gesture.
226
+ const handleTouchCancel = useCallback(() => {
227
+ touchState.current = {};
228
+ }, []);
223
229
  if (!isOpen || items.length === 0)
224
230
  return null;
225
231
  const current = items[index];
226
- return (_jsxs(Div, { layout: "flex", ref: containerRef, className: "fixed inset-0 z-[var(--appkit-z-tooltip)] bg-black/95 flex-col", role: "dialog", "aria-modal": "true", "aria-label": L.lightboxTitle, children: [_jsx(Div, { ref: imageAreaRef, className: "flex-1 relative overflow-hidden select-none", onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, style: {
232
+ return (_jsxs(Div, { layout: "flex", ref: containerRef, className: "fixed inset-0 z-[var(--appkit-z-tooltip)] bg-black/95 flex-col", role: "dialog", "aria-modal": "true", "aria-label": L.lightboxTitle, children: [_jsx(Div, { ref: imageAreaRef, className: "flex-1 relative overflow-hidden select-none", onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchCancel, style: {
227
233
  cursor: zoom > 1 ? (isDragging ? "grabbing" : "grab") : "default",
228
234
  touchAction: "none",
229
235
  }, onClick: (e) => {
@@ -1,6 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  export function MediaSlider({ value, min = 0, max = 100, step = 1, onChange, onChangeEnd, className = "", }) {
3
- return (_jsx("input", { type: "range", min: min, max: max, step: step, value: value, onChange: (e) => onChange?.(parseFloat(e.target.value)), onMouseUp: (e) => onChangeEnd?.(parseFloat(e.target.value)), onTouchEnd: (e) => onChangeEnd?.(parseFloat(e.target.value)), className: [
3
+ return (_jsx("input", { type: "range", min: min, max: max, step: step, value: value, onChange: (e) => onChange?.(parseFloat(e.target.value)), onMouseUp: (e) => onChangeEnd?.(parseFloat(e.target.value)),
4
+ // audit-touch-handler-ok: native <input type="range"> owns its own
5
+ // drag/cancel state; nothing here persists across a cancelled touch.
6
+ onTouchEnd: (e) => onChangeEnd?.(parseFloat(e.target.value)), className: [
4
7
  "w-full h-2 appearance-none cursor-pointer rounded-full",
5
8
  "bg-zinc-200 bg-[var(--appkit-color-border)]",
6
9
  "accent-primary-600 dark:accent-secondary-400",
@@ -89,7 +89,7 @@ export function ImageCropModal({ isOpen, imageUrl, onClose, onSave, initialCropD
89
89
  setZoom(1);
90
90
  setPosition({ x: 50, y: 50 });
91
91
  };
92
- return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: t("cropTitle"), size: "lg", children: _jsxs(Stack, { gap: "3", children: [_jsx(Text, { variant: "secondary", size: "xs", children: t("cropInstruction") }), _jsxs(Div, { ref: containerRef, className: "relative w-full max-w-sm mx-auto aspect-square overflow-hidden cursor-move max-h-[280px] touch-none", surface: "subtle", rounded: "lg", "data-disable-swipe": "true", tabIndex: 0, role: "application", "aria-label": t("cropInstruction"), onKeyDown: handleKeyDown, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [_jsx("img", { src: imageUrl, alt: t("cropPreviewAlt"), draggable: false, className: "absolute select-none pointer-events-none object-cover", style: {
92
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: t("cropTitle"), size: "lg", children: _jsxs(Stack, { gap: "3", children: [_jsx(Text, { variant: "secondary", size: "xs", children: t("cropInstruction") }), _jsxs(Div, { ref: containerRef, className: "relative w-full max-w-sm mx-auto aspect-square overflow-hidden cursor-move max-h-[280px] touch-none", surface: "subtle", rounded: "lg", "data-disable-swipe": "true", tabIndex: 0, role: "application", "aria-label": t("cropInstruction"), onKeyDown: handleKeyDown, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchEnd, children: [_jsx("img", { src: imageUrl, alt: t("cropPreviewAlt"), draggable: false, className: "absolute select-none pointer-events-none object-cover", style: {
93
93
  width: `${zoom * 100}%`,
94
94
  height: `${zoom * 100}%`,
95
95
  left: `${position.x}%`,
@@ -11,7 +11,7 @@ import { normalizeError } from "../../../errors/normalize";
11
11
  import { useState, useRef } from "react";
12
12
  import { useTranslations } from "next-intl";
13
13
  import { useCamera } from "../../../react";
14
- import { Alert, Button, Div, Label, Progress, Row, Spinner, Stack, Text } from "../../../ui";
14
+ import { Alert, Button, Div, Label, Progress, Row, Spinner, Stack, Text, useToast } from "../../../ui";
15
15
  import { MediaImage } from "../MediaImage";
16
16
  import { ImageCropModal } from "../modals/ImageCropModal";
17
17
  import { ImageEditor } from "../modals/ImageEditor";
@@ -51,6 +51,7 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
51
51
  const tUpload = useTranslations("upload");
52
52
  const tMediaEditor = useTranslations("mediaEditor");
53
53
  const { isSupported: isCameraSupported } = useCamera();
54
+ const { showToast } = useToast();
54
55
  const showCamera = captureSource === "camera-only" ||
55
56
  (captureSource === "both" && captureMode === "camera");
56
57
  const showFileInput = captureSource === "file-only" ||
@@ -65,12 +66,18 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
65
66
  onChange?.(url);
66
67
  if (cropData)
67
68
  onCropDataChange?.(cropData);
69
+ return true;
68
70
  }
69
71
  catch (err) {
70
72
  void normalizeError(err);
71
73
  const message = err instanceof Error ? err.message : "Upload failed";
72
74
  setError(message);
73
75
  setPreview(currentImage || "");
76
+ // The crop/advanced-editor modal stays open on failure (callers only
77
+ // close it when this resolves true) — without a toast too, the error
78
+ // was easy to miss below the button the closed modal used to cover.
79
+ showToast(message, "error");
80
+ return false;
74
81
  }
75
82
  finally {
76
83
  setUploading(false);
@@ -97,12 +104,15 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
97
104
  };
98
105
  reader.readAsDataURL(file);
99
106
  };
100
- const handleCropSave = (cropData) => {
101
- setShowCropModal(false);
102
- if (pendingFile)
103
- void performUpload(pendingFile, cropData);
104
- setCropPreviewUrl(null);
105
- setPendingFile(null);
107
+ const handleCropSave = async (cropData) => {
108
+ if (!pendingFile)
109
+ return;
110
+ const ok = await performUpload(pendingFile, cropData);
111
+ if (ok) {
112
+ setShowCropModal(false);
113
+ setCropPreviewUrl(null);
114
+ setPendingFile(null);
115
+ }
106
116
  };
107
117
  const handleCropClose = () => {
108
118
  setShowCropModal(false);
@@ -112,11 +122,13 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
112
122
  setPendingFile(null);
113
123
  }
114
124
  };
115
- const handleAdvancedEditorSave = (croppedFile) => {
116
- setShowAdvancedEditor(false);
117
- setCropPreviewUrl(null);
118
- setPendingFile(null);
119
- void performUpload(croppedFile);
125
+ const handleAdvancedEditorSave = async (croppedFile) => {
126
+ const ok = await performUpload(croppedFile);
127
+ if (ok) {
128
+ setShowAdvancedEditor(false);
129
+ setCropPreviewUrl(null);
130
+ setPendingFile(null);
131
+ }
120
132
  };
121
133
  const openEditor = (url) => {
122
134
  setCropPreviewUrl(url);
@@ -53,5 +53,5 @@ export function MarketplaceOrderCard({ order, links, labels, className = "", var
53
53
  const actionsRow = (_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [(isShipped || isDelivered) &&
54
54
  trackHref &&
55
55
  order.trackingNumber && (_jsx(Button, { variant: "ghost", size: "sm", className: CLS_ACTION_BUTTON, onClick: () => navigate(trackHref), children: mergedLabels.trackOrder })), isDelivered && reviewHref && (_jsx(Button, { variant: "outline", size: "sm", className: CLS_ACTION_BUTTON, onClick: () => navigate(reviewHref), children: mergedLabels.writeReview })), _jsx(Button, { variant: "secondary", size: "sm", className: CLS_ACTION_BUTTON, onClick: () => navigate(detailHref), children: mergedLabels.viewOrder })] }));
56
- return (_jsxs(Div, { rounded: "xl", border: "subtle", shadow: "hover-md", className: `group relative overflow-hidden bg-[var(--appkit-color-surface)] transition-all duration-200 ${isSelected ? "ring-2 ring-primary-500 dark:ring-primary-400" : ""} ${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, "data-section": "marketplaceordercard-div-411", children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: handleSelect, label: isSelected ? "Deselect order" : "Select order", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), isListVariant ? (_jsxs(Row, { align: "center", justify: "between", gap: "md", padding: "md", "data-section": "marketplaceordercard-div-412", children: [_jsx(Div, { layout: "flex", align: "start", gap: "3", paddingX: selectable ? "l-xl" : undefined, className: "min-w-0 flex-1", "data-section": "marketplaceordercard-div-413", children: infoBlock }), _jsxs(Row, { align: "center", gap: "md", className: "flex-shrink-0", "data-section": "marketplaceordercard-div-416", children: [_jsx(Row, { align: "center", gap: "md", "data-section": "marketplaceordercard-div-417", children: statusPriceBlock }), actionsRow] })] })) : (_jsxs(Stack, { gap: "md", padding: "md", "data-section": "marketplaceordercard-div-412", children: [_jsx(Div, { layout: "flex", align: "start", gap: "3", paddingX: selectable ? "l-xl" : undefined, "data-section": "marketplaceordercard-div-413", children: infoBlock }), _jsxs(Stack, { gap: "3", "data-section": "marketplaceordercard-div-416", children: [_jsx(Row, { wrap: true, align: "center", justify: "between", gap: "3", "data-section": "marketplaceordercard-div-417", children: statusPriceBlock }), actionsRow] })] }))] }));
56
+ return (_jsxs(Div, { rounded: "xl", border: "subtle", shadow: "hover-md", className: `group relative overflow-hidden bg-[var(--appkit-color-surface)] transition-all duration-200 ${isSelected ? "ring-2 ring-primary-500 dark:ring-primary-400" : ""} ${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, "data-section": "marketplaceordercard-div-411", children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: handleSelect, label: isSelected ? "Deselect order" : "Select order", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), isListVariant ? (_jsxs(Row, { align: "center", justify: "between", gap: "md", padding: "md", "data-section": "marketplaceordercard-div-412", children: [_jsx(Div, { layout: "flex", align: "start", gap: "3", paddingX: selectable ? "l-xl" : undefined, className: "min-w-0 flex-1", "data-section": "marketplaceordercard-div-413", children: infoBlock }), _jsxs(Row, { align: "center", gap: "md", className: "flex-shrink-0", "data-section": "marketplaceordercard-div-416", children: [_jsx(Row, { align: "center", gap: "md", "data-section": "marketplaceordercard-div-417", children: statusPriceBlock }), actionsRow] })] })) : (_jsxs(Stack, { gap: "md", padding: "md", "data-section": "marketplaceordercard-div-412", children: [_jsx(Div, { layout: "flex", align: "start", gap: "3", paddingX: selectable ? "l-xl" : undefined, "data-section": "marketplaceordercard-div-413", children: infoBlock }), _jsxs(Stack, { gap: "3", "data-section": "marketplaceordercard-div-416", children: [_jsx(Row, { wrap: true, align: "center", justify: "between", gap: "3", "data-section": "marketplaceordercard-div-417", children: statusPriceBlock }), actionsRow] })] }))] }));
57
57
  }
@@ -57,7 +57,7 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
57
57
  // The hook rolls back optimistic state when the mutation fails.
58
58
  }
59
59
  }, [toggle]);
60
- return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: detailHref, className: "absolute inset-0 block", children: _jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", fallback: "Product image" }) }), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { layout: "inline-flex", color: "inverse", size: "xs", weight: "medium", className: "bg-cobalt", rounded: "full", padding: "pill-xs", children: mergedLabels.preOrderBadge }), product.featured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_PREORDER_BADGE, children: mergedLabels.featuredBadge }))] }), _jsxs(Stack, { className: "absolute left-2 top-2 pointer-events-none", gap: "xs", children: [product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: CLS_SALE_BADGE, title: product.partOfBundleTitles?.[0] ? `In bundle: ${product.partOfBundleTitles[0]}` : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_TRENDING_BADGE, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_LIMITED_BADGE, title: "Has variants or sub-listings", children: "Has Variants" }))] }), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
60
+ return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: detailHref, className: "absolute inset-0 block", children: _jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", fallback: "Product image" }) }), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { layout: "inline-flex", color: "inverse", size: "xs", weight: "medium", className: "bg-cobalt", rounded: "full", padding: "pill-xs", children: mergedLabels.preOrderBadge }), product.featured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_PREORDER_BADGE, children: mergedLabels.featuredBadge }))] }), _jsxs(Stack, { className: "absolute left-2 top-2 pointer-events-none", gap: "xs", children: [product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: CLS_SALE_BADGE, title: product.partOfBundleTitles?.[0] ? `In bundle: ${product.partOfBundleTitles[0]}` : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_TRENDING_BADGE, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_LIMITED_BADGE, title: "Has variants or sub-listings", children: "Has Variants" }))] }), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
61
61
  event.stopPropagation();
62
62
  onSelect(product.id, !isSelected);
63
63
  }, className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsx(BaseListingCard.Info, { variant: variant, children: variant === "list" ? (