@mohasinac/appkit 4.27.0 → 4.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
- package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
- package/dist/_internal/server/features/checkout/actions.js +6 -4
- package/dist/_internal/server/features/classified/metadata.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +3 -1
- package/dist/_internal/server/features/live/metadata.js +3 -1
- package/dist/_internal/server/features/refunds/actions.js +3 -2
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/seo/sitemap.js +189 -40
- package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
- package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
- package/dist/_internal/shared/actions/action-registry.js +14 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
- package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
- package/dist/_internal/shared/features/checkout/config.js +6 -3
- package/dist/client.d.ts +32 -2
- package/dist/client.js +37 -2
- package/dist/constants/api-endpoints.d.ts +21 -3
- package/dist/constants/api-endpoints.js +14 -1
- package/dist/constants/field-names.d.ts +2 -2
- package/dist/constants/field-names.js +2 -2
- package/dist/core/newsletter.repository.js +14 -1
- package/dist/features/about/components/FAQPageView.js +1 -1
- package/dist/features/about/components/FeesView.js +1 -1
- package/dist/features/account/components/AddressForm.d.ts +2 -0
- package/dist/features/account/components/AddressForm.js +5 -1
- package/dist/features/account/components/UserBidsView.js +1 -1
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/components/UserSupportView.js +24 -2
- package/dist/features/account/hooks/useAddresses.d.ts +29 -1
- package/dist/features/account/hooks/useAddresses.js +4 -1
- package/dist/features/account/hooks/useProfile.d.ts +1 -1
- package/dist/features/account/hooks/useProfile.js +1 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
- package/dist/features/addresses/repository/addresses.repository.js +19 -0
- package/dist/features/admin/actions/chat-actions.js +5 -1
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminBidsView.js +6 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
- package/dist/features/admin/components/AdminCarouselView.js +1 -1
- package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
- package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminViewCards.js +2 -2
- package/dist/features/admin/components/DataTable.js +2 -2
- package/dist/features/admin/schemas/firestore.js +9 -3
- package/dist/features/auctions/actions/bid-actions.js +45 -4
- package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
- package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
- package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
- package/dist/features/auth/components/SocialAuthButtons.js +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts +23 -1
- package/dist/features/auth/hooks/useAuth.js +27 -1
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +3 -0
- package/dist/features/auth/repository/session.repository.d.ts +16 -1
- package/dist/features/auth/repository/session.repository.js +23 -0
- package/dist/features/auth/repository/user.repository.js +13 -2
- package/dist/features/blog/components/BlogListView.js +2 -2
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
- package/dist/features/cart/components/index.d.ts +1 -1
- package/dist/features/cart/components/index.js +1 -1
- package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
- package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
- package/dist/features/cart/utils/line-total.d.ts +25 -0
- package/dist/features/cart/utils/line-total.js +26 -0
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
- package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
- package/dist/features/categories/components/CategoryTree.js +2 -2
- package/dist/features/contact/components/ContactForm.js +1 -1
- package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
- package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
- package/dist/features/events/components/AdminEventEditorView.js +16 -9
- package/dist/features/events/components/AdminEventEntriesView.js +7 -13
- package/dist/features/events/components/EventRafflesSection.js +14 -2
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
- package/dist/features/events/components/event-entry-detail-fields.js +13 -0
- package/dist/features/faq/components/ContactCTA.js +1 -1
- package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
- package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
- package/dist/features/faq/repository/faqs.repository.js +64 -54
- package/dist/features/faq/schemas/firestore.d.ts +3 -2
- package/dist/features/faq/schemas/firestore.js +2 -2
- package/dist/features/faq/schemas/index.d.ts +3 -3
- package/dist/features/faq/schemas/index.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +2 -2
- package/dist/features/forms/Toggle.js +1 -1
- package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
- package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
- package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
- package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/homepage/lib/section-renderer.js +2 -1
- package/dist/features/layout/AppLayoutShell.js +5 -5
- package/dist/features/layout/BottomActions.js +5 -5
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/MainNavbar.js +1 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
- package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
- package/dist/features/lottery/schemas/config-write.d.ts +141 -0
- package/dist/features/lottery/schemas/config-write.js +139 -0
- package/dist/features/media/MediaPickerModal.js +3 -1
- package/dist/features/media/MediaVideo.js +4 -2
- package/dist/features/media/components/MediaSlider.js +1 -1
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/payments/repository/payout.repository.d.ts +11 -1
- package/dist/features/payments/repository/payout.repository.js +14 -6
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/products/columns/productTableColumns.js +3 -3
- package/dist/features/products/components/CompareOverlay.js +1 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
- package/dist/features/products/components/PrizeDrawsSection.js +5 -1
- package/dist/features/products/components/ProductDetailActions.js +11 -7
- package/dist/features/products/components/ProductDetailPageView.js +3 -3
- package/dist/features/products/components/ProductFeatureBadges.js +2 -2
- package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
- package/dist/features/products/components/ProductFilters.js +15 -8
- package/dist/features/products/components/ProductGrid.js +8 -17
- package/dist/features/products/components/ProductTabs.js +7 -4
- package/dist/features/products/components/ProductTabsShell.js +2 -7
- package/dist/features/products/constants/action-defs.d.ts +21 -4
- package/dist/features/products/constants/action-defs.js +7 -3
- package/dist/features/products/hooks/useRelatedProducts.js +2 -1
- package/dist/features/products/repository/products.repository.d.ts +1 -1
- package/dist/features/products/repository/products.repository.js +27 -13
- package/dist/features/products/schemas/firestore.d.ts +3 -2
- package/dist/features/products/schemas/firestore.js +1 -1
- package/dist/features/reviews/actions/review-actions.d.ts +8 -1
- package/dist/features/reviews/actions/review-actions.js +14 -3
- package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
- package/dist/features/reviews/repository/reviews.repository.js +53 -13
- package/dist/features/reviews/schemas/firestore.d.ts +6 -0
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/search/components/SearchResultsSection.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +3 -1
- package/dist/features/seller/components/SellerBidsView.js +6 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/shell/SectionForm.d.ts +21 -1
- package/dist/features/shell/SectionForm.js +15 -3
- package/dist/features/shell/build-sections.js +10 -1
- package/dist/features/shell/field-ui-meta.d.ts +21 -0
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
- package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
- package/dist/features/stores/actions/store-query-actions.js +25 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/stores/components/StoreNavTabs.js +20 -8
- package/dist/features/support/repository/support.repository.d.ts +12 -0
- package/dist/features/support/repository/support.repository.js +15 -0
- package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
- package/dist/features/support/schemas/ticket-create-form.js +82 -0
- package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
- package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
- package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
- package/dist/features/wishlist/components/index.d.ts +2 -1
- package/dist/features/wishlist/components/index.js +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +19 -4
- package/dist/monitoring/error-tracking.js +39 -5
- package/dist/monitoring/server-logger.js +70 -32
- package/dist/next/components/ErrorView.js +1 -1
- package/dist/next/routing/route-map.d.ts +39 -7
- package/dist/next/routing/route-map.js +34 -4
- package/dist/providers/db-firebase/base.repository.d.ts +30 -0
- package/dist/providers/db-firebase/base.repository.js +56 -9
- package/dist/react/ErrorBoundary.js +9 -1
- package/dist/react/contexts/SessionContext.js +6 -2
- package/dist/react/hooks/useAuthGate.d.ts +22 -0
- package/dist/react/hooks/useAuthGate.js +86 -7
- package/dist/schemas/registry.d.ts +3 -3
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-encrypt.d.ts +13 -5
- package/dist/security/pii-encrypt.js +132 -6
- package/dist/security/pii-schemas.d.ts +20 -4
- package/dist/security/pii-schemas.js +23 -5
- package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
- package/dist/seed/_helpers/product-search-txt.js +33 -0
- package/dist/seed/faq-seed-data.d.ts +16 -0
- package/dist/seed/faq-seed-data.js +22 -1
- package/dist/seed/products-art-seed-data.js +49 -7
- package/dist/seed/products-auctions-seed-data.js +12 -4
- package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
- package/dist/seed/products-classifieds-seed-data.js +65 -9
- package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
- package/dist/seed/products-digital-codes-seed-data.js +65 -9
- package/dist/seed/products-live-items-seed-data.d.ts +5 -0
- package/dist/seed/products-live-items-seed-data.js +33 -5
- package/dist/seed/products-preorders-seed-data.d.ts +1 -1
- package/dist/seed/products-preorders-seed-data.js +6 -2
- package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
- package/dist/seed/products-prize-draws-seed-data.js +57 -8
- package/dist/seed/products-standard-seed-data.js +6 -2
- package/dist/seed/products-stickers-seed-data.js +49 -7
- package/dist/seed/site-settings-seed-data.js +5 -3
- package/dist/seo/json-ld.d.ts +44 -2
- package/dist/seo/json-ld.js +57 -11
- package/dist/styles.css +368 -90
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +1 -1
- package/dist/tokens/themes/default-dark.js +6 -0
- package/dist/tokens/themes/default-light.js +5 -0
- package/dist/tokens/tokens.css +84 -0
- package/dist/ui/DataTable.js +1 -1
- package/dist/ui/DataTable.style.css +3 -3
- package/dist/ui/components/BulkActionBar.style.css +1 -3
- package/dist/ui/components/Button.js +15 -6
- package/dist/ui/components/Dropdown.style.css +1 -4
- package/dist/ui/components/FilterChipGroup.js +1 -1
- package/dist/ui/components/FilterDrawer.style.css +1 -1
- package/dist/ui/components/Icon.d.ts +53 -0
- package/dist/ui/components/Icon.js +41 -0
- package/dist/ui/components/IconButton.d.ts +13 -0
- package/dist/ui/components/IconButton.js +13 -0
- package/dist/ui/components/IconButton.style.css +18 -10
- package/dist/ui/components/ListingLayout.style.css +2 -10
- package/dist/ui/components/ListingToolbar.js +6 -6
- package/dist/ui/components/Modal.js +1 -1
- package/dist/ui/components/PaginatedSelect.style.css +3 -3
- package/dist/ui/components/Pagination.style.css +1 -6
- package/dist/ui/components/RichTextEditor.js +1 -1
- package/dist/ui/components/RowActionMenu.style.css +1 -1
- package/dist/ui/components/SideDrawer.style.css +1 -1
- package/dist/ui/components/Slider.js +1 -1
- package/dist/ui/components/TabBarShell.d.ts +78 -0
- package/dist/ui/components/TabBarShell.js +95 -0
- package/dist/ui/components/Tabs.js +12 -8
- package/dist/ui/components/Tabs.style.css +249 -41
- package/dist/ui/components/ViewToggle.style.css +1 -5
- package/dist/ui/components/tabs-overflow.d.ts +17 -0
- package/dist/ui/components/tabs-overflow.js +142 -0
- package/dist/ui/icons/icon-registry.d.ts +38 -0
- package/dist/ui/icons/icon-registry.js +37 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/media-url.d.ts +27 -4
- package/dist/utils/media-url.js +37 -6
- package/dist/utils/search-txt.d.ts +71 -0
- package/dist/utils/search-txt.js +118 -0
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@ function CategoryChip({ category }) {
|
|
|
23
23
|
const coverImage = category.display?.coverImage;
|
|
24
24
|
const initial = category.name[0]?.toUpperCase() ?? "?";
|
|
25
25
|
const productCount = category.metrics?.totalProductCount ?? category.metrics?.productCount ?? 0;
|
|
26
|
-
return (_jsxs(Link, { href: ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug), className: "group flex w-full min-h-[180px] sm:min-h-[220px] flex-col overflow-hidden rounded-xl border
|
|
26
|
+
return (_jsxs(Link, { href: ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug), className: "group flex w-full min-h-[180px] sm:min-h-[220px] flex-col overflow-hidden rounded-xl border bg-[var(--appkit-color-surface)] shadow-sm transition-all hover:border-primary-300 hover:shadow-md border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:hover:border-primary-600", children: [coverImage && isImageUrl(coverImage) ? (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, surface: "subtle", children: _jsx(Image, { src: resolveMediaUrl(coverImage), alt: category.name, width: 320, height: 180, className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" }) })) : (_jsx(Div, { surface: "muted", className: "aspect-video w-full" })), _jsxs(Stack, { className: `flex-1 ${__P.p3} text-left`, children: [_jsx(Row, { textWeight: "bold", textSize: "sm", className: "mb-2 h-9 w-9 bg-primary-100 text-primary-700 dark:bg-primary-900 dark:text-primary-300", align: "center", justify: "center", rounded: "lg", children: iconSrc && isImageUrl(iconSrc) ? (_jsx(Image, { src: resolveMediaUrl(iconSrc), alt: "", width: 24, height: 24, className: "h-6 w-6 rounded object-cover", "aria-hidden": true })) : iconSrc ? (_jsx(Span, { size: "lg", "aria-hidden": "true", className: "leading-none", children: iconSrc })) : (initial) }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", truncate: 2, children: category.name }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [productCount.toLocaleString(), " items"] }), _jsx(Text, { className: "mt-auto text-primary dark:text-primary-400 pt-[0.75rem]", size: "xs", weight: "medium", children: "Browse category \u2192" })] })] }));
|
|
27
27
|
}
|
|
28
28
|
const CTA_CLASSES = {
|
|
29
29
|
filled: "rounded-lg bg-[var(--appkit-color-primary)] px-[var(--appkit-space-5)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white hover:opacity-90",
|
|
@@ -9,5 +9,5 @@ export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, cta
|
|
|
9
9
|
if (isLoading) {
|
|
10
10
|
return (_jsx(Section, { className: `relative overflow-hidden pt-[0px] pb-[var(--appkit-space-16)] md:pt-[0px] md:pb-[6rem] ${className}`, paddingX: "x-md", children: _jsxs(Div, { className: "animate-pulse max-w-4xl mx-auto text-left", children: [_jsx(Div, { className: "h-6 w-52 mb-6", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-20 mb-4 max-w-2xl", surface: "subtle", rounded: "lg" }), _jsx(Div, { className: "h-6 mb-8 max-w-lg", surface: "subtle", rounded: "lg" }), _jsxs(Row, { justify: "start", gap: "md", children: [_jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" }), _jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" })] })] }) }));
|
|
11
11
|
}
|
|
12
|
-
return (_jsxs(Section, { className: `relative overflow-hidden pt-[0px] pb-[var(--appkit-space-20)] md:pt-[0px] md:pb-[7rem] ${className}`, paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-
|
|
12
|
+
return (_jsxs(Section, { className: `relative overflow-hidden pt-[0px] pb-[var(--appkit-space-20)] md:pt-[0px] md:pb-[7rem] ${className}`, paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-primary-surface transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center} p-[2.5rem]`, children: [_jsx(SiteMark, { title: brandLogoText || "LetItRip.in", size: "xl" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "xl" })] })] }) })] }) })] }));
|
|
13
13
|
}
|
|
@@ -28,6 +28,7 @@ import { GoogleReviewsSection } from "../components/GoogleReviewsSection";
|
|
|
28
28
|
// S-SBUNI-3 2026-05-13 — FeaturedBundlesSection rebuilt against
|
|
29
29
|
// categoryType:"bundle" rows on the categories collection.
|
|
30
30
|
import { FeaturedBundlesSection } from "../components/FeaturedBundlesSection";
|
|
31
|
+
import { HomepageSectionBoundary } from "../components/HomepageSectionBoundary";
|
|
31
32
|
import { PrizeDrawsSection } from "../../products/components/PrizeDrawsSection";
|
|
32
33
|
import { EventRafflesSection } from "../../events/components/EventRafflesSection";
|
|
33
34
|
import { CollectionCardsSection } from "../components/CollectionCardsSection";
|
|
@@ -200,6 +201,6 @@ export function renderSection(section, adSlots, newsletterFormSlot, faqItems, sl
|
|
|
200
201
|
if (!sectionElement)
|
|
201
202
|
return null;
|
|
202
203
|
const adSlotKey = AD_SLOT_MAP[section.type];
|
|
203
|
-
return (_jsxs(React.Fragment, { children: [sectionElement, adSlots && adSlotKey !== undefined && adSlotKey in adSlots && adSlots[adSlotKey]] }, section.id));
|
|
204
|
+
return (_jsxs(React.Fragment, { children: [_jsx(HomepageSectionBoundary, { sectionId: section.id, sectionType: section.type, children: sectionElement }), adSlots && adSlotKey !== undefined && adSlotKey in adSlots && adSlots[adSlotKey]] }, section.id));
|
|
204
205
|
}
|
|
205
206
|
export { AnnouncementBar, hashBannerMessage };
|
|
@@ -76,11 +76,11 @@ function SidebarUserHeader({ user, onClose, }) {
|
|
|
76
76
|
}
|
|
77
77
|
: user.photoURL
|
|
78
78
|
? { url: user.photoURL, position: { x: 50, y: 50 }, zoom: 1 }
|
|
79
|
-
: null, size: "md", alt: user.displayName || "User", displayName: user.displayName, email: user.email }), user.role && !isBuyerUser(user) && (_jsx(Row, { textWeight: "bold", className: `absolute -bottom-0.5 -right-0.5 w-4 h-4 border-2 border-white text-white text-[9px] leading-none select-none ${ROLE_DOT_BG_CLASS[user.role] ?? "bg-[var(--appkit-color-text-muted)]"}`, align: "center", justify: "center", rounded: "full", title: user.role.charAt(0).toUpperCase() + user.role.slice(1), "aria-label": user.role, children: user.role.charAt(0).toUpperCase() }))] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "medium", children: user.displayName || "User" }), _jsx(Text, { className: "truncate", color: "muted", size: "xs", children: user.email || "" })] })] }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: onClose, className: "flex-shrink-0 rounded-full p-[var(--appkit-space-2)] text-[var(--appkit-color-text-muted)] hover:bg-
|
|
79
|
+
: null, size: "md", alt: user.displayName || "User", displayName: user.displayName, email: user.email }), user.role && !isBuyerUser(user) && (_jsx(Row, { textWeight: "bold", className: `absolute -bottom-0.5 -right-0.5 w-4 h-4 border-2 border-white text-white text-[9px] leading-none select-none ${ROLE_DOT_BG_CLASS[user.role] ?? "bg-[var(--appkit-color-text-muted)]"}`, align: "center", justify: "center", rounded: "full", title: user.role.charAt(0).toUpperCase() + user.role.slice(1), "aria-label": user.role, children: user.role.charAt(0).toUpperCase() }))] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "medium", children: user.displayName || "User" }), _jsx(Text, { className: "truncate", color: "muted", size: "xs", children: user.email || "" })] })] }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: onClose, className: "flex-shrink-0 rounded-full p-[var(--appkit-space-2)] text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-900 text-[var(--appkit-color-text-muted)] dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", 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" }) }) })] }));
|
|
80
80
|
}
|
|
81
81
|
/** Sidebar header when no user is logged in — title + close button. */
|
|
82
82
|
function SidebarGuestHeader({ sidebarTitle, onClose, }) {
|
|
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-
|
|
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-surface-hover hover:text-zinc-900 text-[var(--appkit-color-text-muted)] dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", 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" }) }) })] }));
|
|
84
84
|
}
|
|
85
85
|
function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions, user, activeHref, profileHref, userOrdersHref, userWishlistHref, userSettingsHref, adminHref, storeHref, sellerHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, theme, activeThemeName, toggleTheme, onLogout, onAfterLogout, }) {
|
|
86
86
|
const hasLegacyItems = sidebarItems.length > 0;
|
|
@@ -100,7 +100,7 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
|
|
|
100
100
|
storeDashboard: sidebarProfileLabels?.storeDashboard ?? sidebarProfileLabels?.sellerDashboard ?? "Store Dashboard",
|
|
101
101
|
logout: sidebarProfileLabels?.logout ?? "Logout",
|
|
102
102
|
};
|
|
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)]
|
|
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)] transition-colors hover:bg-primary-surface hover:text-primary-800 text-[var(--appkit-color-text-muted)] dark:hover:text-secondary-300";
|
|
104
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
105
|
const profileLinks = [
|
|
106
106
|
{ href: profileHref },
|
|
@@ -122,9 +122,9 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
|
|
|
122
122
|
return (_jsxs(Stack, { gap: "lg", children: [!isAuthenticated && sidebarPrimaryActions && sidebarPrimaryActions.length > 0 && (_jsx(Stack, { gap: "sm", children: sidebarPrimaryActions.map((action) => (_jsx(TextLink, { href: action.href, variant: "none", className: [
|
|
123
123
|
"block w-full rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2-5)] text-center text-[length:var(--appkit-text-sm)] font-semibold transition-all duration-200 hover:scale-[1.02] shadow-sm",
|
|
124
124
|
action.variant === "outline"
|
|
125
|
-
? "border
|
|
125
|
+
? "border hover:bg-surface-hover border-[var(--appkit-color-border)] text-[var(--appkit-color-text)]"
|
|
126
126
|
: "bg-primary text-white hover:bg-primary-600 dark:bg-primary dark:hover:bg-primary-600 btn-glow",
|
|
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-
|
|
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-surface 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 }))] }))] }));
|
|
128
128
|
}
|
|
129
129
|
// ─── Main component ────────────────────────────────────────────────────────────
|
|
130
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, showBottomNav = true, onTourStart, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
|
|
@@ -20,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
20
20
|
* Layout rules:
|
|
21
21
|
* - Hidden on lg+ screens by default — desktop shows inline action panels. A page can
|
|
22
22
|
* opt in by passing `desktop: "after-scroll" | "always"` to `useBottomActions`;
|
|
23
|
-
*
|
|
23
|
+
*"after-scroll" brings the bar back once the primary CTA has scrolled out of view.
|
|
24
24
|
* - The bar slides up with a 300 ms ease-out transition; `pointer-events-none`
|
|
25
25
|
* while off-screen.
|
|
26
26
|
*
|
|
@@ -57,10 +57,10 @@ function BulkPickerPanel({ bulkActions, selectedActionId, pickerOpen, onSelect,
|
|
|
57
57
|
return (_jsxs(Button, { role: "option", "aria-selected": isSelected, type: "button", variant: "ghost", disabled: action.disabled || action.loading, onClick: () => onSelect(action.id), className: [
|
|
58
58
|
"w-full flex items-center gap-[var(--appkit-space-3)] px-[var(--appkit-space-5)] py-[var(--appkit-space-3-5)] text-left text-[length:var(--appkit-text-sm)] font-medium transition-colors rounded-none",
|
|
59
59
|
i > 0 ? "border-t border-zinc-100/80 border-[var(--appkit-color-border-subtle)]" : "",
|
|
60
|
-
isSelected ? "
|
|
60
|
+
isSelected ? " bg-[var(--appkit-color-surface-input)]" : "",
|
|
61
61
|
action.variant === "danger"
|
|
62
62
|
? "text-error hover:bg-error-surface"
|
|
63
|
-
: "text-[var(--appkit-color-text)] hover:bg-
|
|
63
|
+
: "text-[var(--appkit-color-text)] hover:bg-surface-hover",
|
|
64
64
|
action.disabled || action.loading ? "opacity-50 cursor-not-allowed" : "",
|
|
65
65
|
]
|
|
66
66
|
.filter(Boolean)
|
|
@@ -204,11 +204,11 @@ export default function BottomActions() {
|
|
|
204
204
|
isVisible ? "" : "pointer-events-none",
|
|
205
205
|
]
|
|
206
206
|
.filter(Boolean)
|
|
207
|
-
.join(" "), children: [isBulkMode && (_jsx(Div, { className: "h-[3px] w-full bg-[image:var(--appkit-gradient-brand-tri)]" })), secondaryLabel && !isBulkMode && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: secondaryLabel }) })), infoLabel && !isBulkMode && !hasInfoPanel && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }) })), infoLabel && !isBulkMode && hasInfoPanel && (_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setInfoOpen((o) => !o), "aria-expanded": infoOpen, "aria-controls": infoPanelId, className: "w-full flex items-center justify-between gap-[var(--appkit-space-2)] rounded-none border-b border-[var(--appkit-color-border)]/80 px-[var(--appkit-space-4)] pt-[var(--appkit-space-2)] pb-[var(--appkit-space-1)] min-h-0", children: [_jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }), _jsxs(Span, { className: "flex-shrink-0 flex items-center gap-[var(--appkit-space-1)]", children: [_jsx(Text, { as: "span", size: "xs", color: "muted", children: infoOpen ? "Hide" : "Details" }), infoOpen ? (_jsx(ChevronDown, { className: "w-4 h-4 text-zinc-400", "aria-hidden": "true" })) : (_jsx(ChevronUp, { className: "w-4 h-4 text-zinc-400", "aria-hidden": "true" }))] })] })), _jsx(Row, { className: `${BOTTOM_NAV_HEIGHT}`, gap: "sm", padding: "x-sm", children: isBulkMode && bulk ? (_jsxs(_Fragment, { children: [_jsxs(Button, { rounded: "full", gap: "xs", type: "button", variant: "ghost", onClick: dispatchBulkClear, className: "inline- flex-shrink-0 bg-primary-50 hover:bg-primary-
|
|
207
|
+
.join(" "), children: [isBulkMode && (_jsx(Div, { className: "h-[3px] w-full bg-[image:var(--appkit-gradient-brand-tri)]" })), secondaryLabel && !isBulkMode && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: secondaryLabel }) })), infoLabel && !isBulkMode && !hasInfoPanel && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }) })), infoLabel && !isBulkMode && hasInfoPanel && (_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setInfoOpen((o) => !o), "aria-expanded": infoOpen, "aria-controls": infoPanelId, className: "w-full flex items-center justify-between gap-[var(--appkit-space-2)] rounded-none border-b border-[var(--appkit-color-border)]/80 px-[var(--appkit-space-4)] pt-[var(--appkit-space-2)] pb-[var(--appkit-space-1)] min-h-0", children: [_jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }), _jsxs(Span, { className: "flex-shrink-0 flex items-center gap-[var(--appkit-space-1)]", children: [_jsx(Text, { as: "span", size: "xs", color: "muted", children: infoOpen ? "Hide" : "Details" }), infoOpen ? (_jsx(ChevronDown, { className: "w-4 h-4 text-zinc-400", "aria-hidden": "true" })) : (_jsx(ChevronUp, { className: "w-4 h-4 text-zinc-400", "aria-hidden": "true" }))] })] })), _jsx(Row, { className: `${BOTTOM_NAV_HEIGHT}`, gap: "sm", padding: "x-sm", children: isBulkMode && bulk ? (_jsxs(_Fragment, { children: [_jsxs(Button, { rounded: "full", gap: "xs", type: "button", variant: "ghost", onClick: dispatchBulkClear, className: "inline- flex-shrink-0 bg-primary-50 hover:bg-primary-surface active:bg-primary-200 dark:bg-primary-950/30 text-primary-700 dark:text-primary-300 pl-2 pr-3 h-8 border border-primary-200/70 dark:border-primary-800/50 transition-colors min-h-0", "aria-label": "Clear selection", children: [_jsx(X, { className: "w-3.5 h-3.5 flex-shrink-0", "aria-hidden": "true" }), _jsx(Span, { size: "xs", weight: "semibold", className: "tabular-nums whitespace-nowrap leading-none", children: bulk.noun
|
|
208
208
|
? `${bulk.selectedCount} ${bulk.noun}`
|
|
209
209
|
: `${bulk.selectedCount} selected` })] }), bulkActions.length > 0 && (_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setPickerOpen((o) => !o), "aria-haspopup": "listbox", "aria-expanded": pickerOpen, className: [
|
|
210
210
|
"flex-1 min-w-0 h-10 flex items-center gap-[var(--appkit-space-2)] px-[var(--appkit-space-3)] rounded-lg border text-[length:var(--appkit-text-sm)] font-medium transition-colors",
|
|
211
|
-
"
|
|
211
|
+
" hover:bg-[var(--appkit-color-surface)] active:bg-zinc-200 bg-[var(--appkit-color-surface-input)] dark:hover:bg-slate-700/60",
|
|
212
212
|
"border-[var(--appkit-color-border)]",
|
|
213
213
|
selectedAction?.variant === "danger"
|
|
214
214
|
? "text-error"
|
|
@@ -38,5 +38,5 @@ export function BottomSheet({ open, onClose, title, children, className, maxHeig
|
|
|
38
38
|
}, [open, handleEscape]);
|
|
39
39
|
if (!open)
|
|
40
40
|
return null;
|
|
41
|
-
return (_jsxs(Div, { className: "fixed inset-0 z-50 lg:hidden", role: "presentation", children: [_jsx(Div, { surface: "overlay-sm", className: "absolute inset-0 backdrop-blur-sm", "aria-hidden": "true", onClick: onClose }), _jsxs(Stack, { role: "dialog", "aria-modal": "true", "aria-label": title, className: `absolute bottom-0 inset-x-0 rounded-t-2xl border-t border-[var(--appkit-color-border-subtle)] shadow-2xl ${maxHeight} animate-slide-up${className ? ` ${className}` : ""}`, surface: "default", children: [_jsxs(Row, { border: "subtle", justify: "between", className: "border-b shrink-0", padding: "inline", children: [_jsx(Div, { surface: "skeleton", className: "absolute top-2 left-1/2 -translate-x-1/2 w-10 h-1 bg-[var(--appkit-color-border)]", rounded: "full" }), title && (_jsx(Span, { size: "sm", weight: "semibold", className: "mt-2", color: "primary", children: title })), _jsx(Button, { rounded: "lg", type: "button", onClick: onClose, "aria-label": "Close", className: "ml-auto mt-2 p-[var(--appkit-space-1-5)] text-[var(--appkit-color-text-muted)] hover:text-zinc-700
|
|
41
|
+
return (_jsxs(Div, { className: "fixed inset-0 z-50 lg:hidden", role: "presentation", children: [_jsx(Div, { surface: "overlay-sm", className: "absolute inset-0 backdrop-blur-sm", "aria-hidden": "true", onClick: onClose }), _jsxs(Stack, { role: "dialog", "aria-modal": "true", "aria-label": title, className: `absolute bottom-0 inset-x-0 rounded-t-2xl border-t border-[var(--appkit-color-border-subtle)] shadow-2xl ${maxHeight} animate-slide-up${className ? ` ${className}` : ""}`, surface: "default", children: [_jsxs(Row, { border: "subtle", justify: "between", className: "border-b shrink-0", padding: "inline", children: [_jsx(Div, { surface: "skeleton", className: "absolute top-2 left-1/2 -translate-x-1/2 w-10 h-1 bg-[var(--appkit-color-border)]", rounded: "full" }), title && (_jsx(Span, { size: "sm", weight: "semibold", className: "mt-2", color: "primary", children: title })), _jsx(Button, { rounded: "lg", type: "button", onClick: onClose, "aria-label": "Close", className: "ml-auto mt-2 p-[var(--appkit-space-1-5)] text-[var(--appkit-color-text-muted)] hover:text-zinc-700 text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text-muted)] hover:bg-surface-hover transition-colors min-h-[44px] min-w-[44px] justify-center", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto} overscroll-contain pb-[env(safe-area-inset-bottom)]`, children: children })] })] }));
|
|
42
42
|
}
|
|
@@ -12,6 +12,6 @@ export function MainNavbar({ navItems, hiddenNavItems = [], inline = false, hasD
|
|
|
12
12
|
icon: item.icon,
|
|
13
13
|
highlighted: item.highlighted,
|
|
14
14
|
}));
|
|
15
|
-
const dashboardToggle = hasDashboardNav && onToggleDashboardNav ? (_jsx("button", { type: "button", onClick: onToggleDashboardNav, "aria-label": "Toggle dashboard navigation", className: "flex items-center justify-center w-8 h-8 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-
|
|
15
|
+
const dashboardToggle = hasDashboardNav && onToggleDashboardNav ? (_jsx("button", { type: "button", onClick: onToggleDashboardNav, "aria-label": "Toggle dashboard navigation", className: "flex items-center justify-center w-8 h-8 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-800 dark:hover:text-zinc-100 transition-colors", children: _jsxs("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: "M3 5a2 2 0 012-2h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5z" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 3v18" })] }) })) : undefined;
|
|
16
16
|
return (_jsx(NavbarLayout, { items: items, activeHref: pathname, inline: inline, rightSlot: dashboardToggle }));
|
|
17
17
|
}
|
|
@@ -12,7 +12,7 @@ function DefaultNavItem({ item, isActive, }) {
|
|
|
12
12
|
? "border border-primary-400/40 dark:border-secondary-400/30 text-primary-700 dark:text-secondary-400 bg-primary-50/80 dark:bg-secondary-900/30 px-[var(--appkit-space-3)] transition-colors duration-150"
|
|
13
13
|
: isActive
|
|
14
14
|
? "bg-primary-50 dark:bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
|
|
15
|
-
: "text-[var(--appkit-color-text-muted)] hover:bg-
|
|
15
|
+
: "text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-950 dark:hover:text-white transition-colors duration-150 px-[var(--appkit-space-3)]";
|
|
16
16
|
return (_jsxs(Link, { href: item.href, "aria-current": isActive ? "page" : undefined, className: `flex items-center gap-[var(--appkit-space-1-5)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] rounded-lg font-medium transition-colors duration-150 whitespace-nowrap ${activeClasses}`, children: [item.icon, _jsx(Span, { children: item.label })] }));
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
|
|
4
4
|
/** Shared icon-button class for all action buttons in the title bar. */
|
|
5
|
-
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-
|
|
5
|
+
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-surface hover:text-primary-700 dark:hover:text-secondary-400 transition-colors";
|
|
6
6
|
/** Badge counter class for wishlist/cart counts.
|
|
7
7
|
*
|
|
8
8
|
* The horizontal offset lives in `.appkit-hand-badge` (HandMode.style.css), NOT
|
|
@@ -29,7 +29,7 @@ const countBadge = "appkit-hand-badge absolute -top-0.5 flex items-center justif
|
|
|
29
29
|
*/
|
|
30
30
|
export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
|
|
31
31
|
// ── Element builders ────────────────────────────────────────────────────────
|
|
32
|
-
const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-
|
|
32
|
+
const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-surface transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
|
|
33
33
|
const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
|
|
34
34
|
const tourBtn = onTourStart ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Start product tour", onClick: onTourStart, className: iconBtn, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) })) : null;
|
|
35
35
|
const hamburgerBtn = !hideSidebarToggle ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": sidebarOpen ? "Close menu" : hasDashboardNav ? "Open dashboard navigation" : "Open menu", "aria-expanded": sidebarOpen, "aria-controls": "secondary-sidebar", onClick: hasDashboardNav && onToggleDashboardNav ? onToggleDashboardNav : onToggleSidebar, className: iconBtn, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: sidebarOpen ? (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" })) : (_jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" })) }) })) : null;
|
|
@@ -46,7 +46,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
46
46
|
const authButtonsEl = !user && (loginHref || registerHref) ? (
|
|
47
47
|
// Mirrors with TB1 so Register (the primary CTA) keeps the outer, more
|
|
48
48
|
// reachable edge instead of ending up tucked inside after the flip.
|
|
49
|
-
_jsxs(Row, { gap: "xs", reverse: "hand", 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-
|
|
49
|
+
_jsxs(Row, { gap: "xs", reverse: "hand", 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-surface", 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;
|
|
50
50
|
const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
|
|
51
51
|
// ── Render ───────────────────────────────────────────────────────────────────
|
|
52
52
|
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", reverse: "hand", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandShortName ?? brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex 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(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", reverse: "hand", children: [devSlot, navSlot, 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
|
|
@@ -16,6 +16,16 @@ export interface UseFormBottomActionsOptions {
|
|
|
16
16
|
* `Form` itself renders, so the ambient lookup would find the parent's
|
|
17
17
|
* context or none at all.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* Destructive action for this record, mirrored from `SectionForm`. Pushed
|
|
21
|
+
* to the FRONT of the bar so it is furthest from where the thumb rests on
|
|
22
|
+
* the primary action.
|
|
23
|
+
*/
|
|
24
|
+
destructiveAction?: {
|
|
25
|
+
label: string;
|
|
26
|
+
onClick: () => void;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
};
|
|
19
29
|
ctx?: FormShellContextValue;
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
@@ -53,5 +63,5 @@ export interface UseFormBottomActionsOptions {
|
|
|
53
63
|
* overlay tells us, because neither portal leaves a detectable trace in
|
|
54
64
|
* the DOM at the form's position.
|
|
55
65
|
*/
|
|
56
|
-
export declare function useFormBottomActions({ onSubmit, onCancel, submitLabel, cancelLabel, isLoading, disabled, enabled, ctx: ctxProp, }: UseFormBottomActionsOptions): void;
|
|
66
|
+
export declare function useFormBottomActions({ onSubmit, onCancel, submitLabel, cancelLabel, isLoading, disabled, enabled, destructiveAction, ctx: ctxProp, }: UseFormBottomActionsOptions): void;
|
|
57
67
|
export default useFormBottomActions;
|
|
@@ -40,7 +40,7 @@ import { useBottomActions } from "./useBottomActions";
|
|
|
40
40
|
* overlay tells us, because neither portal leaves a detectable trace in
|
|
41
41
|
* the DOM at the form's position.
|
|
42
42
|
*/
|
|
43
|
-
export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save", cancelLabel = "Cancel", isLoading = false, disabled = false, enabled = true, ctx: ctxProp, }) {
|
|
43
|
+
export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save", cancelLabel = "Cancel", isLoading = false, disabled = false, enabled = true, destructiveAction, ctx: ctxProp, }) {
|
|
44
44
|
const ambient = useContext(FormShellContext);
|
|
45
45
|
const ctx = ctxProp ?? ambient;
|
|
46
46
|
const insideOverlay = useIsInsideOverlay();
|
|
@@ -51,6 +51,20 @@ export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save",
|
|
|
51
51
|
if (!active)
|
|
52
52
|
return [];
|
|
53
53
|
const list = [];
|
|
54
|
+
/*
|
|
55
|
+
* Destructive FIRST, so it is furthest from the thumb's resting position
|
|
56
|
+
* on the primary action. It never grows, so Save keeps the width.
|
|
57
|
+
*/
|
|
58
|
+
if (destructiveAction) {
|
|
59
|
+
list.push({
|
|
60
|
+
id: "form-destructive",
|
|
61
|
+
label: destructiveAction.label,
|
|
62
|
+
variant: "danger",
|
|
63
|
+
grow: false,
|
|
64
|
+
onClick: destructiveAction.onClick,
|
|
65
|
+
disabled: destructiveAction.disabled || isLoading,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
54
68
|
if (onCancel) {
|
|
55
69
|
list.push({
|
|
56
70
|
id: "form-cancel",
|
|
@@ -71,7 +85,7 @@ export function useFormBottomActions({ onSubmit, onCancel, submitLabel = "Save",
|
|
|
71
85
|
onClick: () => void onSubmit(),
|
|
72
86
|
});
|
|
73
87
|
return list;
|
|
74
|
-
}, [active, onCancel, cancelLabel, submitLabel, isLoading, disabled, onSubmit]);
|
|
88
|
+
}, [active, onCancel, cancelLabel, submitLabel, isLoading, disabled, onSubmit, destructiveAction]);
|
|
75
89
|
useBottomActions({
|
|
76
90
|
actions,
|
|
77
91
|
// Encodes the count on purpose — see (1) in the header.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { LotteryConfig } from "../types/index";
|
|
3
|
+
/**
|
|
4
|
+
* What an admin may say about one slot. Booking state is deliberately absent
|
|
5
|
+
* — see the header. `.strict()`, so `isBooked` is a 400.
|
|
6
|
+
*/
|
|
7
|
+
export declare const lotterySlotWriteSchema: z.ZodObject<{
|
|
8
|
+
slotNumber: z.ZodNumber;
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
image: z.ZodOptional<z.ZodString>;
|
|
11
|
+
price: z.ZodNumber;
|
|
12
|
+
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
name: string;
|
|
14
|
+
price: number;
|
|
15
|
+
slotNumber: number;
|
|
16
|
+
image?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
name: string;
|
|
19
|
+
price: number;
|
|
20
|
+
slotNumber: number;
|
|
21
|
+
image?: string | undefined;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const lotteryConfigWriteSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
24
|
+
slots: z.ZodArray<z.ZodObject<{
|
|
25
|
+
slotNumber: z.ZodNumber;
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
image: z.ZodOptional<z.ZodString>;
|
|
28
|
+
price: z.ZodNumber;
|
|
29
|
+
}, "strict", z.ZodTypeAny, {
|
|
30
|
+
name: string;
|
|
31
|
+
price: number;
|
|
32
|
+
slotNumber: number;
|
|
33
|
+
image?: string | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
name: string;
|
|
36
|
+
price: number;
|
|
37
|
+
slotNumber: number;
|
|
38
|
+
image?: string | undefined;
|
|
39
|
+
}>, "many">;
|
|
40
|
+
pricingMode: z.ZodEnum<["uniform", "variable"]>;
|
|
41
|
+
uniformPrice: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
drawWindowDurationMinutes: z.ZodNumber;
|
|
43
|
+
maxPullsPerTransaction: z.ZodNumber;
|
|
44
|
+
maxPullsPerUser: z.ZodNumber;
|
|
45
|
+
}, "strict", z.ZodTypeAny, {
|
|
46
|
+
slots: {
|
|
47
|
+
name: string;
|
|
48
|
+
price: number;
|
|
49
|
+
slotNumber: number;
|
|
50
|
+
image?: string | undefined;
|
|
51
|
+
}[];
|
|
52
|
+
pricingMode: "uniform" | "variable";
|
|
53
|
+
drawWindowDurationMinutes: number;
|
|
54
|
+
maxPullsPerTransaction: number;
|
|
55
|
+
maxPullsPerUser: number;
|
|
56
|
+
uniformPrice?: number | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
slots: {
|
|
59
|
+
name: string;
|
|
60
|
+
price: number;
|
|
61
|
+
slotNumber: number;
|
|
62
|
+
image?: string | undefined;
|
|
63
|
+
}[];
|
|
64
|
+
pricingMode: "uniform" | "variable";
|
|
65
|
+
drawWindowDurationMinutes: number;
|
|
66
|
+
maxPullsPerTransaction: number;
|
|
67
|
+
maxPullsPerUser: number;
|
|
68
|
+
uniformPrice?: number | undefined;
|
|
69
|
+
}>, {
|
|
70
|
+
slots: {
|
|
71
|
+
name: string;
|
|
72
|
+
price: number;
|
|
73
|
+
slotNumber: number;
|
|
74
|
+
image?: string | undefined;
|
|
75
|
+
}[];
|
|
76
|
+
pricingMode: "uniform" | "variable";
|
|
77
|
+
drawWindowDurationMinutes: number;
|
|
78
|
+
maxPullsPerTransaction: number;
|
|
79
|
+
maxPullsPerUser: number;
|
|
80
|
+
uniformPrice?: number | undefined;
|
|
81
|
+
}, {
|
|
82
|
+
slots: {
|
|
83
|
+
name: string;
|
|
84
|
+
price: number;
|
|
85
|
+
slotNumber: number;
|
|
86
|
+
image?: string | undefined;
|
|
87
|
+
}[];
|
|
88
|
+
pricingMode: "uniform" | "variable";
|
|
89
|
+
drawWindowDurationMinutes: number;
|
|
90
|
+
maxPullsPerTransaction: number;
|
|
91
|
+
maxPullsPerUser: number;
|
|
92
|
+
uniformPrice?: number | undefined;
|
|
93
|
+
}>, {
|
|
94
|
+
slots: {
|
|
95
|
+
name: string;
|
|
96
|
+
price: number;
|
|
97
|
+
slotNumber: number;
|
|
98
|
+
image?: string | undefined;
|
|
99
|
+
}[];
|
|
100
|
+
pricingMode: "uniform" | "variable";
|
|
101
|
+
drawWindowDurationMinutes: number;
|
|
102
|
+
maxPullsPerTransaction: number;
|
|
103
|
+
maxPullsPerUser: number;
|
|
104
|
+
uniformPrice?: number | undefined;
|
|
105
|
+
}, {
|
|
106
|
+
slots: {
|
|
107
|
+
name: string;
|
|
108
|
+
price: number;
|
|
109
|
+
slotNumber: number;
|
|
110
|
+
image?: string | undefined;
|
|
111
|
+
}[];
|
|
112
|
+
pricingMode: "uniform" | "variable";
|
|
113
|
+
drawWindowDurationMinutes: number;
|
|
114
|
+
maxPullsPerTransaction: number;
|
|
115
|
+
maxPullsPerUser: number;
|
|
116
|
+
uniformPrice?: number | undefined;
|
|
117
|
+
}>;
|
|
118
|
+
export type LotteryConfigWriteInput = z.infer<typeof lotteryConfigWriteSchema>;
|
|
119
|
+
/** Why a merge was refused. Distinct from a validation failure. */
|
|
120
|
+
export interface LotteryMergeRejection {
|
|
121
|
+
ok: false;
|
|
122
|
+
code: "BOOKED_SLOT_REMOVED";
|
|
123
|
+
message: string;
|
|
124
|
+
slotNumbers: number[];
|
|
125
|
+
}
|
|
126
|
+
export type LotteryMergeResult = {
|
|
127
|
+
ok: true;
|
|
128
|
+
config: LotteryConfig;
|
|
129
|
+
} | LotteryMergeRejection;
|
|
130
|
+
/**
|
|
131
|
+
* Apply an admin's edit to the stored config, carrying every booking across.
|
|
132
|
+
*
|
|
133
|
+
* `existing` is undefined when the lottery is being created, in which case
|
|
134
|
+
* there is nothing to preserve and every slot starts unbooked.
|
|
135
|
+
*
|
|
136
|
+
* `totalSlots` is DERIVED from `slots.length` and never accepted from input —
|
|
137
|
+
* a caller-supplied count that disagrees with the array is exactly the
|
|
138
|
+
* mirror-drift trap (Root Cause #42), and `totalSlots` is what the fullness
|
|
139
|
+
* check reads.
|
|
140
|
+
*/
|
|
141
|
+
export declare function mergeLotteryConfig(input: LotteryConfigWriteInput, existing?: LotteryConfig): LotteryMergeResult;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `lotteryConfig` had NO write schema. The only authoring UI
|
|
3
|
+
* (`LotteryAdminEditView`) was orphaned, and the route it would have
|
|
4
|
+
* posted to — `PATCH /api/admin/events/[id]` — is `.passthrough()`, so
|
|
5
|
+
* the whole config wrote through completely unvalidated.
|
|
6
|
+
* WHAT: The write contract, plus `mergeLotteryConfig` — the one function that
|
|
7
|
+
* is allowed to turn an admin's edit into a stored config.
|
|
8
|
+
*
|
|
9
|
+
* ## 🛑 The author-side slot shape CANNOT carry booking state
|
|
10
|
+
*
|
|
11
|
+
* `LotteryAdminEditView.handleSubmit` sent `isBooked: false` and `weight: 0`
|
|
12
|
+
* for every slot, and dropped `bookedByUserId` / `bookedByDisplayName` /
|
|
13
|
+
* `bookedByUserLotteryNumber` entirely. Against a `.passthrough()` PATCH that
|
|
14
|
+
* is not a validation gap — it is a **destructive write**: the first save of
|
|
15
|
+
* a live lottery marks every purchased slot as available again, and the
|
|
16
|
+
* buyers who paid for them are simply gone from the record.
|
|
17
|
+
*
|
|
18
|
+
* So the input type deliberately has NO booking fields at all. An admin
|
|
19
|
+
* editing a lottery is describing PRIZES, not attendance, and the schema
|
|
20
|
+
* refuses to express the latter — a `.strict()` object, so sending
|
|
21
|
+
* `isBooked` is a 400 rather than something a merge has to defend against.
|
|
22
|
+
*
|
|
23
|
+
* `weight` is absent for the same reason: it is derived from price
|
|
24
|
+
* server-side and is never client input.
|
|
25
|
+
*
|
|
26
|
+
* ## Merging is BY `slotNumber`, not by index
|
|
27
|
+
*
|
|
28
|
+
* Array position is not identity. An admin who deletes slot 3 shifts every
|
|
29
|
+
* later slot's index by one, and an index-wise merge would hand slot 7's
|
|
30
|
+
* buyer the prize that used to be slot 8. `slotNumber` is the stable key
|
|
31
|
+
* users actually see and pull against.
|
|
32
|
+
*
|
|
33
|
+
* A slot that is booked cannot be REMOVED either. Dropping it would erase a
|
|
34
|
+
* paid entry with no trace; the merge rejects that rather than silently
|
|
35
|
+
* carrying the slot along, because an admin who thinks they deleted a prize
|
|
36
|
+
* and did not is a worse outcome than an error.
|
|
37
|
+
*
|
|
38
|
+
* EXPORTS: lotteryConfigWriteSchema, lotterySlotWriteSchema,
|
|
39
|
+
* mergeLotteryConfig, type LotteryConfigWriteInput
|
|
40
|
+
*
|
|
41
|
+
* @tag domain:lottery
|
|
42
|
+
* @tag layer:schema
|
|
43
|
+
* @tag pattern:none
|
|
44
|
+
* @tag access:isomorphic
|
|
45
|
+
* @tag consumers:/api/admin/events/[id]/lottery-config,LotteryAdminEditView
|
|
46
|
+
* @tag sideEffects:none
|
|
47
|
+
*/
|
|
48
|
+
import { z } from "zod";
|
|
49
|
+
/** Mirrors `LotteryPricingMode`. */
|
|
50
|
+
const pricingModeSchema = z.enum(["uniform", "variable"]);
|
|
51
|
+
/**
|
|
52
|
+
* What an admin may say about one slot. Booking state is deliberately absent
|
|
53
|
+
* — see the header. `.strict()`, so `isBooked` is a 400.
|
|
54
|
+
*/
|
|
55
|
+
export const lotterySlotWriteSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
slotNumber: z.coerce.number().int().min(1).max(200),
|
|
58
|
+
name: z.string().min(1, "Every slot needs a prize name.").max(200),
|
|
59
|
+
image: z.string().max(2000).optional(),
|
|
60
|
+
price: z.coerce.number().min(0, "A price cannot be negative."),
|
|
61
|
+
})
|
|
62
|
+
.strict();
|
|
63
|
+
export const lotteryConfigWriteSchema = z
|
|
64
|
+
.object({
|
|
65
|
+
slots: z
|
|
66
|
+
.array(lotterySlotWriteSchema)
|
|
67
|
+
.min(1, "A lottery needs at least one slot.")
|
|
68
|
+
.max(200, "A lottery cannot exceed 200 slots."),
|
|
69
|
+
pricingMode: pricingModeSchema,
|
|
70
|
+
uniformPrice: z.coerce.number().min(0).optional(),
|
|
71
|
+
drawWindowDurationMinutes: z.coerce.number().int().min(1).max(1440),
|
|
72
|
+
maxPullsPerTransaction: z.coerce.number().int().min(1).max(200),
|
|
73
|
+
maxPullsPerUser: z.coerce.number().int().min(1).max(200),
|
|
74
|
+
})
|
|
75
|
+
.strict()
|
|
76
|
+
.refine((c) => new Set(c.slots.map((s) => s.slotNumber)).size === c.slots.length, { message: "Two slots share a slot number.", path: ["slots"] })
|
|
77
|
+
.refine((c) => c.pricingMode !== "uniform" || typeof c.uniformPrice === "number", {
|
|
78
|
+
message: "Uniform pricing needs a price.",
|
|
79
|
+
path: ["uniformPrice"],
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Apply an admin's edit to the stored config, carrying every booking across.
|
|
83
|
+
*
|
|
84
|
+
* `existing` is undefined when the lottery is being created, in which case
|
|
85
|
+
* there is nothing to preserve and every slot starts unbooked.
|
|
86
|
+
*
|
|
87
|
+
* `totalSlots` is DERIVED from `slots.length` and never accepted from input —
|
|
88
|
+
* a caller-supplied count that disagrees with the array is exactly the
|
|
89
|
+
* mirror-drift trap (Root Cause #42), and `totalSlots` is what the fullness
|
|
90
|
+
* check reads.
|
|
91
|
+
*/
|
|
92
|
+
export function mergeLotteryConfig(input, existing) {
|
|
93
|
+
const priorBySlot = new Map();
|
|
94
|
+
for (const slot of existing?.slots ?? [])
|
|
95
|
+
priorBySlot.set(slot.slotNumber, slot);
|
|
96
|
+
const incoming = new Set(input.slots.map((s) => s.slotNumber));
|
|
97
|
+
const droppedBooked = [...priorBySlot.values()]
|
|
98
|
+
.filter((s) => s.isBooked && !incoming.has(s.slotNumber))
|
|
99
|
+
.map((s) => s.slotNumber);
|
|
100
|
+
if (droppedBooked.length > 0) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
code: "BOOKED_SLOT_REMOVED",
|
|
104
|
+
message: `Slot ${droppedBooked.join(", ")} has already been pulled and cannot be ` +
|
|
105
|
+
`removed. Reopen it first if the pull needs to be undone.`,
|
|
106
|
+
slotNumbers: droppedBooked,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const slots = input.slots.map((s) => {
|
|
110
|
+
const prior = priorBySlot.get(s.slotNumber);
|
|
111
|
+
return {
|
|
112
|
+
slotNumber: s.slotNumber,
|
|
113
|
+
name: s.name,
|
|
114
|
+
image: s.image?.trim() || undefined,
|
|
115
|
+
price: input.pricingMode === "uniform" ? (input.uniformPrice ?? 0) : s.price,
|
|
116
|
+
// Server-derived, never input. Recomputed by the pricing pass; carried
|
|
117
|
+
// from the prior slot so an edit does not zero a live weighting.
|
|
118
|
+
weight: prior?.weight ?? 0,
|
|
119
|
+
// 🛑 THE WHOLE POINT: booking state comes from the STORED slot, never
|
|
120
|
+
// from the request. A slot the admin renames keeps its buyer.
|
|
121
|
+
isBooked: prior?.isBooked ?? false,
|
|
122
|
+
bookedByUserId: prior?.bookedByUserId,
|
|
123
|
+
bookedByDisplayName: prior?.bookedByDisplayName,
|
|
124
|
+
bookedByUserLotteryNumber: prior?.bookedByUserLotteryNumber,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
config: {
|
|
130
|
+
slots,
|
|
131
|
+
totalSlots: slots.length,
|
|
132
|
+
pricingMode: input.pricingMode,
|
|
133
|
+
uniformPrice: input.pricingMode === "uniform" ? input.uniformPrice : undefined,
|
|
134
|
+
drawWindowDurationMinutes: input.drawWindowDurationMinutes,
|
|
135
|
+
maxPullsPerTransaction: input.maxPullsPerTransaction,
|
|
136
|
+
maxPullsPerUser: input.maxPullsPerUser,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -57,8 +57,10 @@ export function MediaPickerModal({ isOpen, onClose, onSelect, accept = "image/*"
|
|
|
57
57
|
setExistingFiles(data.data.files);
|
|
58
58
|
})
|
|
59
59
|
.catch((err) => {
|
|
60
|
+
// normalizeError covers Zod/Api/Firebase shapes an `instanceof Error`
|
|
61
|
+
// check misses, so those stop collapsing to the generic fallback.
|
|
60
62
|
if (!cancelled)
|
|
61
|
-
setExistingError(err
|
|
63
|
+
setExistingError(normalizeError(err).message || "Failed to load");
|
|
62
64
|
})
|
|
63
65
|
.finally(() => {
|
|
64
66
|
if (!cancelled)
|