@mohasinac/appkit 4.53.1 → 4.54.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/.build-stamp.json +3 -3
- package/dist/_internal/client/theme/ThemeProvider.js +15 -1
- package/dist/_internal/server/features/auth/capabilities.js +7 -5
- package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +22 -6
- package/dist/_internal/server/features/checkout/actions.js +96 -51
- package/dist/_internal/server/features/checkout/index.d.ts +1 -1
- package/dist/_internal/server/features/checkout/index.js +1 -1
- package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
- package/dist/_internal/server/features/orders/adapters.js +1 -3
- package/dist/_internal/server/features/price-index/market.d.ts +57 -0
- package/dist/_internal/server/features/price-index/market.js +120 -0
- package/dist/_internal/server/features/products/list-public.d.ts +12 -0
- package/dist/_internal/server/features/products/list-public.js +10 -1
- package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
- package/dist/_internal/server/features/products/listing-tab-counts.js +184 -10
- package/dist/_internal/server/features/search/index-build/sources.js +43 -2
- package/dist/_internal/server/features/site-settings/adapters.js +1 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +0 -1
- package/dist/_internal/server/features/stores/tabs.d.ts +7 -2
- package/dist/_internal/server/features/stores/tabs.js +5 -3
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +2 -5
- package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
- package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +0 -2
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +0 -1
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +0 -1
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +0 -1
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +0 -1
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +0 -1
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +0 -1
- package/dist/_internal/server/notifications/channel-health.d.ts +7 -3
- package/dist/_internal/server/notifications/channel-health.js +2 -2
- package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
- package/dist/_internal/server/notifications/send-guard.js +2 -4
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +128 -28
- package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
- package/dist/_internal/shared/checkout/order-math.js +1 -1
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
- package/dist/_internal/shared/features/checkout/config.d.ts +0 -2
- package/dist/_internal/shared/features/checkout/config.js +0 -2
- package/dist/_internal/shared/features/messaging/config.d.ts +7 -5
- package/dist/_internal/shared/features/messaging/config.js +1 -4
- package/dist/_internal/shared/features/site-settings/fees.d.ts +0 -2
- package/dist/_internal/shared/features/site-settings/fees.js +0 -2
- package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
- package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
- package/dist/_internal/shared/fees/calculator.d.ts +0 -8
- package/dist/_internal/shared/fees/calculator.js +0 -7
- package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
- package/dist/_internal/shared/listing-types/tiers.js +150 -0
- package/dist/_internal/shared/search/match.js +28 -2
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +25 -4
- package/dist/client.js +36 -5
- package/dist/constants/api-endpoints.d.ts +28 -15
- package/dist/constants/api-endpoints.js +14 -8
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/constants/notification-types.d.ts +1 -1
- package/dist/constants/notification-types.js +1 -1
- package/dist/core/integration-keys.d.ts +5 -5
- package/dist/core/integration-keys.js +21 -10
- package/dist/features/about/components/FeesView.js +0 -6
- package/dist/features/about/components/GuidesHubView.js +15 -2
- package/dist/features/account/components/NotificationPreferencesPanel.js +3 -4
- package/dist/features/account/components/UserOrdersView.js +26 -7
- package/dist/features/account/types/index.d.ts +0 -2
- package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
- package/dist/features/addresses/utils/postal-autofill.js +54 -0
- package/dist/features/admin/actions/notification-actions.d.ts +2 -8
- package/dist/features/admin/actions/notification-actions.js +9 -104
- package/dist/features/admin/components/AdminGuideHubView.js +1 -8
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +0 -2
- package/dist/features/admin/components/AdminSectionsView.js +3 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +22 -68
- package/dist/features/admin/components/AdminStoreEditorView.js +72 -34
- package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
- package/dist/features/admin/components/OwnerQuickActions.js +142 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +13 -4
- package/dist/features/admin/components/sections/icon-options.d.ts +21 -0
- package/dist/features/admin/components/sections/icon-options.js +23 -0
- package/dist/features/admin/components/sections/sectionTemplates.js +4 -1
- package/dist/features/admin/constants/site-settings-tabs.d.ts +7 -12
- package/dist/features/admin/constants/site-settings-tabs.js +6 -12
- package/dist/features/admin/repository/site-settings.repository.js +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
- package/dist/features/admin/schemas/admin-user-form.d.ts +0 -61
- package/dist/features/admin/schemas/admin-user-form.js +3 -44
- package/dist/features/admin/schemas/firestore.d.ts +28 -54
- package/dist/features/admin/schemas/firestore.js +40 -18
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -10
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
- package/dist/features/auth/checkout-value-otp.d.ts +1 -1
- package/dist/features/auth/checkout-value-otp.js +1 -1
- package/dist/features/auth/permissions/constants.d.ts +29 -2
- package/dist/features/auth/permissions/constants.js +67 -1
- package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
- package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
- package/dist/features/cart/components/CartDrawer.js +3 -2
- package/dist/features/cart/components/CartGroupLineRow.js +2 -1
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +0 -2
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +2 -5
- package/dist/features/cart/components/StoreAddonsPicker.js +4 -5
- package/dist/features/cart/hooks/useAddToCart.js +10 -5
- package/dist/features/cart/repository/cart.repository.js +1 -2
- package/dist/features/cart/schemas/firestore.d.ts +1 -2
- package/dist/features/cart/utils/pending-ops.d.ts +11 -1
- package/dist/features/cart/utils/pending-ops.js +14 -2
- package/dist/features/categories/api/[id]/price-guidance/route.js +1 -24
- package/dist/features/categories/components/BrandDetailPageView.js +15 -6
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/BrandDetailTabs.js +18 -3
- package/dist/features/categories/components/BundleCollage.js +3 -2
- package/dist/features/categories/components/BundleDetailView.js +2 -1
- package/dist/features/categories/components/BundleItemsPicker.js +3 -4
- package/dist/features/categories/components/CategoryDetailPageView.js +19 -8
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +29 -7
- package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
- package/dist/features/categories/components/CategoryGrid.js +9 -2
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
- package/dist/features/classified/components/ClassifiedDetailPageView.js +12 -2
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +12 -2
- package/dist/features/events/components/EventCard.d.ts +11 -1
- package/dist/features/events/components/EventCard.js +9 -2
- package/dist/features/grouped/components/GroupedListingDetailView.js +3 -1
- package/dist/features/homepage/components/CTABannerSection.d.ts +3 -1
- package/dist/features/homepage/components/CTABannerSection.js +6 -3
- package/dist/features/homepage/components/HeroCarousel.js +8 -6
- package/dist/features/homepage/components/MarketplaceHomepageView.js +33 -7
- package/dist/features/homepage/components/SectionCarousel.js +13 -5
- package/dist/features/homepage/components/SecurityHighlightsSection.d.ts +8 -3
- package/dist/features/homepage/components/SecurityHighlightsSection.js +2 -2
- package/dist/features/homepage/components/StatsCounterSection.d.ts +9 -4
- package/dist/features/homepage/components/StatsCounterSection.js +2 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/TrustFeaturesSection.d.ts +18 -4
- package/dist/features/homepage/components/TrustFeaturesSection.js +5 -5
- package/dist/features/homepage/components/WelcomeSection.d.ts +13 -2
- package/dist/features/homepage/components/WelcomeSection.js +3 -3
- package/dist/features/homepage/components/WhatsAppCommunitySection.d.ts +28 -0
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +42 -6
- package/dist/features/homepage/components/WhatsAppGlyph.d.ts +20 -0
- package/dist/features/homepage/components/WhatsAppGlyph.js +20 -0
- package/dist/features/homepage/constants/hero-carousel-styles.d.ts +8 -6
- package/dist/features/homepage/constants/hero-carousel-styles.js +8 -6
- package/dist/features/homepage/constants/section-copy.d.ts +12 -10
- package/dist/features/homepage/constants/section-copy.js +28 -6
- package/dist/features/homepage/constants/stat-icons.d.ts +12 -0
- package/dist/features/homepage/constants/stat-icons.js +17 -0
- package/dist/features/homepage/constants/trust-icon-aliases.d.ts +8 -0
- package/dist/features/homepage/constants/trust-icon-aliases.js +75 -0
- package/dist/features/homepage/lib/section-defaults.d.ts +16 -1
- package/dist/features/homepage/lib/section-defaults.js +24 -4
- package/dist/features/homepage/lib/section-renderer.js +48 -8
- package/dist/features/homepage/schemas/firestore.d.ts +18 -1
- package/dist/features/layout/AppLayoutShell.js +2 -1
- package/dist/features/layout/BottomActions.d.ts +1 -0
- package/dist/features/layout/BottomActions.js +330 -0
- package/dist/features/layout/BottomChrome.d.ts +45 -0
- package/dist/features/layout/BottomChrome.js +5 -17
- package/dist/features/layout/SideActionDock.js +36 -13
- package/dist/features/layout/index.d.ts +2 -7
- package/dist/features/layout/index.js +7 -7
- package/dist/features/layout/useIsDesktop.d.ts +36 -0
- package/dist/features/layout/useIsDesktop.js +48 -0
- package/dist/features/live/components/LiveItemDetailPageView.js +12 -2
- package/dist/features/orders/actions/refund-actions.js +0 -1
- package/dist/features/orders/components/OrderAddonBadges.d.ts +1 -3
- package/dist/features/orders/components/OrderAddonBadges.js +3 -4
- package/dist/features/orders/components/OrderPaymentSummary.js +15 -2
- package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
- package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
- package/dist/features/orders/components/index.d.ts +2 -1
- package/dist/features/orders/components/index.js +1 -1
- package/dist/features/orders/repository/orders.repository.d.ts +0 -8
- package/dist/features/orders/repository/orders.repository.js +0 -2
- package/dist/features/orders/schemas/firestore.d.ts +17 -9
- package/dist/features/orders/types/index.d.ts +2 -5
- package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
- package/dist/features/orders/utils/order-announcement-message.js +16 -0
- package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
- package/dist/features/orders/utils/payment-proof-message.js +17 -0
- package/dist/features/parts/components/PartDetailPageView.js +96 -1
- package/dist/features/parts/schemas/firestore.d.ts +247 -0
- package/dist/features/parts/schemas/firestore.js +6 -6
- package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -10
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +21 -0
- package/dist/features/price-index/components/PriceIndexToolView.js +230 -27
- package/dist/features/price-index/lookup-target.d.ts +48 -0
- package/dist/features/price-index/lookup-target.js +107 -0
- package/dist/features/products/api/[id]/route.js +8 -1
- package/dist/features/products/api/route.js +16 -4
- package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
- package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
- package/dist/features/products/components/CompareOverlay.d.ts +2 -1
- package/dist/features/products/components/CompareOverlay.js +2 -1
- package/dist/features/products/components/GroupSettingsPanel.js +2 -1
- package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
- package/dist/features/products/components/InteractiveProductCard.js +16 -4
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
- package/dist/features/products/components/ProductDetailPageView.js +34 -4
- package/dist/features/products/components/ProductGrid.d.ts +14 -1
- package/dist/features/products/components/ProductGrid.js +22 -5
- package/dist/features/products/components/ProductsIndexListing.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +2 -1
- package/dist/features/products/repository/products.repository.d.ts +1 -0
- package/dist/features/products/repository/products.repository.js +5 -1
- package/dist/features/products/utils/group-member-image.d.ts +9 -2
- package/dist/features/products/utils/group-member-image.js +2 -1
- package/dist/features/reviews/api/[id]/route.js +83 -23
- package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
- package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
- package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
- package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
- package/dist/features/reviews/components/index.d.ts +4 -0
- package/dist/features/reviews/components/index.js +2 -0
- package/dist/features/scams/schemas/index.d.ts +10 -10
- package/dist/features/search/action-index/settings-entries.js +0 -9
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +49 -3
- package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
- package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
- package/dist/features/seller/components/SellerProductsView.js +3 -2
- package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
- package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +1 -6
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +1 -10
- package/dist/features/store-extensions/schemas/firestore.d.ts +1 -17
- package/dist/features/store-extensions/schemas/firestore.js +0 -10
- package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +0 -1
- package/dist/features/stores/components/StoreGuideHubView.js +2 -8
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -6
- package/dist/features/stores/components/index.d.ts +0 -2
- package/dist/features/stores/components/index.js +0 -1
- package/dist/features/stores/repository/store.repository.d.ts +1 -26
- package/dist/features/stores/repository/store.repository.js +5 -60
- package/dist/features/stores/schemas/firestore.d.ts +6 -25
- package/dist/features/stores/schemas/firestore.js +6 -3
- package/dist/features/stores/schemas/index.d.ts +1 -87
- package/dist/features/stores/schemas/index.js +10 -15
- package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -48
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +0 -40
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +114 -2
- package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +0 -16
- package/dist/features/tester/seed-data/authored/selling__seller-guide.js +1 -1
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +0 -79
- package/dist/features/tester/seed-data/authored/selling__seller-orders.js +1 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +427 -69
- package/dist/index.d.ts +20 -32
- package/dist/index.js +34 -43
- package/dist/next/routing/route-map.d.ts +2 -8
- package/dist/next/routing/route-map.js +21 -4
- package/dist/providers/db-firebase/base.repository.d.ts +1 -1
- package/dist/providers/db-firebase/base.repository.js +1 -1
- package/dist/providers/payment-razorpay/index.d.ts +125 -0
- package/dist/providers/payment-razorpay/index.js +232 -0
- package/dist/providers/payment-razorpay/signature.d.ts +53 -0
- package/dist/providers/payment-razorpay/signature.js +71 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
- package/dist/providers/shipping-shiprocket/index.js +356 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +3 -0
- package/dist/react/hooks/useCanSeePrices.d.ts +0 -27
- package/dist/react/hooks/useCanSeePrices.js +25 -0
- package/dist/repositories/index.d.ts +1 -1
- package/dist/repositories/index.js +1 -1
- package/dist/schemas/registry.d.ts +10 -115
- package/dist/schemas/registry.js +1 -2
- package/dist/security/pii-encrypt.js +1 -1
- package/dist/seed/_helpers/category-forest.js +27 -0
- package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -86
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +13412 -3020
- package/dist/seed/carousel-slides-seed-data.js +47 -10
- package/dist/seed/categories-seed-data.js +48 -6
- package/dist/seed/faq-seed-data.js +2 -2
- package/dist/seed/homepage-sections-seed-data.js +286 -54
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +1 -1
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +1 -2
- package/dist/seed/model-composition-seed-data.js +6 -8
- package/dist/seed/part-glossary/chunk0.d.ts +9 -0
- package/dist/seed/part-glossary/chunk0.js +35448 -0
- package/dist/seed/part-glossary/chunk1.d.ts +9 -0
- package/dist/seed/part-glossary/chunk1.js +40494 -0
- package/dist/seed/part-glossary/chunk2.d.ts +9 -0
- package/dist/seed/part-glossary/chunk2.js +51242 -0
- package/dist/seed/part-glossary/chunk3.d.ts +9 -0
- package/dist/seed/part-glossary/chunk3.js +53337 -0
- package/dist/seed/part-glossary/chunk4.d.ts +9 -0
- package/dist/seed/part-glossary/chunk4.js +51454 -0
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +23 -47483
- package/dist/seed/products-beyblade-x-seed-data.js +36 -1
- package/dist/seed/site-settings-seed-data.js +34 -35
- package/dist/seed/store-extensions-seed-data.d.ts +1 -2
- package/dist/seed/store-extensions-seed-data.js +1 -23
- package/dist/seed/stores-seed-data.js +0 -1
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +24 -22
- package/dist/styles.css +392 -43
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +1 -1
- package/dist/tokens/themes/default-light.js +2 -1
- package/dist/tokens/tokens.css +6 -2
- package/dist/ui/components/Button.d.ts +11 -0
- package/dist/ui/components/Button.js +11 -0
- package/dist/ui/components/Button.style.css +29 -0
- package/dist/ui/components/CarouselNav.d.ts +57 -0
- package/dist/ui/components/CarouselNav.js +25 -0
- package/dist/ui/components/CarouselNav.style.css +265 -0
- package/dist/ui/components/GatedPrice.js +30 -3
- package/dist/ui/components/GatedPrice.style.css +43 -40
- package/dist/ui/components/HorizontalScroller.d.ts +12 -1
- package/dist/ui/components/HorizontalScroller.js +43 -3
- package/dist/ui/components/ImageMosaic.d.ts +8 -1
- package/dist/ui/components/ImageMosaic.js +3 -2
- package/dist/ui/components/ImageMosaic.style.css +22 -0
- package/dist/ui/components/Layout.style.css +6 -0
- package/dist/ui/components/Semantic.d.ts +3 -2
- package/dist/ui/components/Semantic.js +1 -0
- package/dist/ui/components/Typography.d.ts +2 -0
- package/dist/ui/components/Typography.js +2 -0
- package/dist/ui/components/Typography.style.css +19 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +3 -1
- package/dist/ui/index.js +4 -0
- package/package.json +3 -2
- package/scripts/seed-cli.mjs +133 -27
- package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
- package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
- package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
- package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
- package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
- package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
- package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
- package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
- package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
- package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
- package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
- package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
- package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
- package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
- package/dist/features/whatsapp-bot/components/index.js +0 -2
- package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
- package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
- package/dist/features/whatsapp-bot/index.d.ts +0 -3
- package/dist/features/whatsapp-bot/index.js +0 -3
- package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
- package/dist/features/whatsapp-bot/manifest.js +0 -13
- package/dist/features/whatsapp-bot/messages/en.json +0 -7
- package/dist/features/whatsapp-bot/server.d.ts +0 -7
- package/dist/features/whatsapp-bot/server.js +0 -7
- package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
- package/dist/features/whatsapp-bot/types/index.js +0 -1
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { Aside, Button, Div, Heading, Row, Span, Stack, Text, TextLink } from "../../../ui/index.js";
|
|
4
4
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
5
5
|
import { QuantityStepper } from "../../../ui/components/QuantityStepper.js";
|
|
6
|
+
import { GatedPrice } from "../../../ui/components/GatedPrice.js";
|
|
6
7
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
7
8
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
8
9
|
import { useHandMode } from "../../../_internal/client/hand-mode/index.js";
|
|
@@ -19,11 +20,11 @@ export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock =
|
|
|
19
20
|
return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: `min-w-0 transition-opacity ${isOutOfStock ? "opacity-60" : ""}`, children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: item.meta.image && (_jsx(MediaImage, { src: item.meta.image, alt: item.meta.title, size: "thumbnail" })) }), _jsxs(Stack, { justify: "between", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", className: "min-w-0", align: "start", children: [href ? (_jsx(TextLink, { href: href, target: "_blank", rel: "noopener noreferrer", weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: item.meta.title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: item.meta.title })), isOutOfStock && (_jsx(Span, { padding: "pill-2xs", color: "error", surface: "danger-surface", weight: "semibold", className: "flex-shrink-0 text-[10px] tracking-wide", rounded: "default", transform: "uppercase", children: "Out of Stock" }))] }), item.meta.attributes &&
|
|
20
21
|
Object.keys(item.meta.attributes).length > 0 && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", truncate: 1, children: Object.entries(item.meta.attributes)
|
|
21
22
|
.map(([k, v]) => `${k}: ${v}`)
|
|
22
|
-
.join(", ") })), _jsxs(Row, { justify: "between", className: "min-w-0", gap: "sm", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsx(QuantityStepper, { value: item.quantity, onChange: (next) => onQtyChange(item.id, next), min: 1, ariaLabel: `Quantity for ${item.meta.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
|
|
23
|
+
.join(", ") })), _jsxs(Row, { justify: "between", className: "min-w-0", gap: "sm", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: _jsx(GatedPrice, { children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }) }), onQtyChange && !isOutOfStock && (_jsx(QuantityStepper, { value: item.quantity, onChange: (next) => onQtyChange(item.id, next), min: 1, ariaLabel: `Quantity for ${item.meta.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
|
|
23
24
|
}
|
|
24
25
|
export function CartDrawer({ isOpen, onClose, items, subtotal = 0, currency, isLoading, onQtyChange, onRemove, onCheckout, labels = {}, }) {
|
|
25
26
|
const { hand } = useHandMode();
|
|
26
27
|
if (!isOpen)
|
|
27
28
|
return null;
|
|
28
|
-
return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: `fixed inset-y-0 ${hand === "left" ? "left-0" : "right-0"} z-50 w-full max-w-sm`, children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, reverse: hand === "left", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: `fixed inset-y-0 ${hand === "left" ? "left-0" : "right-0"} z-50 w-full max-w-sm`, children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, reverse: hand === "left", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: _jsx(GatedPrice, { children: formatCurrency(subtotal, currency) }) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
|
|
29
30
|
}
|
|
@@ -20,6 +20,7 @@ import { useState } from "react";
|
|
|
20
20
|
import { Button, Div, Row, Span, Stack, Text, TextLink } from "../../../ui/index.js";
|
|
21
21
|
import { QuantityStepper } from "../../../ui/components/QuantityStepper.js";
|
|
22
22
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
23
|
+
import { GatedPrice, PricesOnly } from "../../../ui/components/GatedPrice.js";
|
|
23
24
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
24
25
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
25
26
|
const __O = { hidden: "overflow-hidden" };
|
|
@@ -29,5 +30,5 @@ export function CartGroupLineRow({ id, title, image, currency = "INR", lineKind,
|
|
|
29
30
|
const onePrice = members.reduce((sum, m) => sum + m.unitPrice * m.quantity, 0);
|
|
30
31
|
const lineTotal = onePrice * quantity;
|
|
31
32
|
const unitCount = members.reduce((sum, m) => sum + m.quantity, 0);
|
|
32
|
-
return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: "min-w-0", children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: image && _jsx(MediaImage, { src: image, alt: title, size: "thumbnail" }) }), _jsxs(Stack, { justify: "between", gap: "xs", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", align: "start", className: "min-w-0", children: [href ? (_jsx(TextLink, { href: href, weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: title })), _jsx(Span, { padding: "pill-2xs", surface: "subtle", color: "muted", rounded: "default", className: "flex-shrink-0 text-[10px] tracking-wide", transform: "uppercase", children: lineKind === "bundle" ? "Bundle" : "Group" })] }), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsOpen((v) => !v), "aria-expanded": isOpen, className: "self-start text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-primary)] hover:underline", children: [isOpen ? "▼" : "▶", " ", members.length, " item", members.length === 1 ? "" : "s", unitCount !== members.length ? ` · ${unitCount} units` : ""] }), isOpen && (_jsx(Stack, { gap: "xs", className: "w-full", children: members.map((m) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "min-w-0", children: [_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0 flex-1", children: [_jsx(Div, { className: `h-10 w-10 flex-shrink-0 ${__O.hidden}`, rounded: "default", surface: "muted", children: m.image && _jsx(MediaImage, { src: m.image, alt: m.title, size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", truncate: 1, color: "primary", children: m.title }), _jsxs(Text, { size: "xs", color: "muted", children: [formatCurrency(m.unitPrice, currency), lineKind === "bundle" ? ` × ${m.quantity}` : ""] })] })] }), lineKind === "group" && onMemberQtyChange && !locked && (_jsxs(Row, { gap: "xs", align: "center", className: "flex-shrink-0", children: [_jsx(QuantityStepper, { value: m.quantity, onChange: (next) => onMemberQtyChange(id, m.productId, next), min: 0, max: maxByProductId?.[m.productId], ariaLabel: `Quantity for ${m.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-group-member"].ariaLabel, onClick: () => onMemberQtyChange(id, m.productId, 0), className: "text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" })] })), lineKind === "bundle" && (_jsxs(Span, { size: "xs", color: "muted", className: "flex-shrink-0", children: ["\u00D7 ", m.quantity] }))] }, m.productId))) })), _jsxs(Row, { justify: "between", gap: "sm", className: "min-w-0", children: [_jsx(Text, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(lineTotal, currency) }), lineKind === "bundle" && onQtyChange && !locked && (_jsx(QuantityStepper, { value: quantity, onChange: (next) => onQtyChange(id, next), min: 1, ariaLabel: `Number of copies of ${title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }))] })] }), onRemove && !locked && (_jsx(Button, { onClick: () => onRemove(id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: "self-start text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" }))] }));
|
|
33
|
+
return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: "min-w-0", children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: image && _jsx(MediaImage, { src: image, alt: title, size: "thumbnail" }) }), _jsxs(Stack, { justify: "between", gap: "xs", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", align: "start", className: "min-w-0", children: [href ? (_jsx(TextLink, { href: href, weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: title })), _jsx(Span, { padding: "pill-2xs", surface: "subtle", color: "muted", rounded: "default", className: "flex-shrink-0 text-[10px] tracking-wide", transform: "uppercase", children: lineKind === "bundle" ? "Bundle" : "Group" })] }), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsOpen((v) => !v), "aria-expanded": isOpen, className: "self-start text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-primary)] hover:underline", children: [isOpen ? "▼" : "▶", " ", members.length, " item", members.length === 1 ? "" : "s", unitCount !== members.length ? ` · ${unitCount} units` : ""] }), isOpen && (_jsx(Stack, { gap: "xs", className: "w-full", children: members.map((m) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "min-w-0", children: [_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0 flex-1", children: [_jsx(Div, { className: `h-10 w-10 flex-shrink-0 ${__O.hidden}`, rounded: "default", surface: "muted", children: m.image && _jsx(MediaImage, { src: m.image, alt: m.title, size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", truncate: 1, color: "primary", children: m.title }), _jsxs(Text, { size: "xs", color: "muted", children: [_jsx(PricesOnly, { children: formatCurrency(m.unitPrice, currency) }), lineKind === "bundle" ? ` × ${m.quantity}` : ""] })] })] }), lineKind === "group" && onMemberQtyChange && !locked && (_jsxs(Row, { gap: "xs", align: "center", className: "flex-shrink-0", children: [_jsx(QuantityStepper, { value: m.quantity, onChange: (next) => onMemberQtyChange(id, m.productId, next), min: 0, max: maxByProductId?.[m.productId], ariaLabel: `Quantity for ${m.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-group-member"].ariaLabel, onClick: () => onMemberQtyChange(id, m.productId, 0), className: "text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" })] })), lineKind === "bundle" && (_jsxs(Span, { size: "xs", color: "muted", className: "flex-shrink-0", children: ["\u00D7 ", m.quantity] }))] }, m.productId))) })), _jsxs(Row, { justify: "between", gap: "sm", className: "min-w-0", children: [_jsx(Text, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: _jsx(GatedPrice, { children: formatCurrency(lineTotal, currency) }) }), lineKind === "bundle" && onQtyChange && !locked && (_jsx(QuantityStepper, { value: quantity, onChange: (next) => onQtyChange(id, next), min: 1, ariaLabel: `Number of copies of ${title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }))] })] }), onRemove && !locked && (_jsx(Button, { onClick: () => onRemove(id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: "self-start text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" }))] }));
|
|
33
34
|
}
|
|
@@ -21,7 +21,6 @@ export interface CartPriceBreakdownStore {
|
|
|
21
21
|
subtotal: number;
|
|
22
22
|
shippingFee: number;
|
|
23
23
|
codHandlingFee: number;
|
|
24
|
-
whatsappNotifyFee: number;
|
|
25
24
|
giftWrapFee: number;
|
|
26
25
|
shipmentProtectionFee: number;
|
|
27
26
|
saleProtectionFee: number;
|
|
@@ -38,7 +37,6 @@ export interface CartPriceBreakdownData {
|
|
|
38
37
|
subtotal: number;
|
|
39
38
|
shippingFee: number;
|
|
40
39
|
codHandlingFee: number;
|
|
41
|
-
whatsappNotifyFee: number;
|
|
42
40
|
giftWrapFee: number;
|
|
43
41
|
shipmentProtectionFee: number;
|
|
44
42
|
saleProtectionFee: number;
|
|
@@ -21,5 +21,5 @@ export function CartPriceBreakdown({ preview, itemCount, currency = "INR", unava
|
|
|
21
21
|
const subtotalLabel = itemCount !== undefined
|
|
22
22
|
? `Subtotal (${itemCount} item${itemCount !== 1 ? "s" : ""}${storeCount > 1 ? `, ${storeCount} stores` : ""})`
|
|
23
23
|
: "Subtotal";
|
|
24
|
-
return (_jsxs(Stack, { gap: "xs", className: className, children: [_jsx(Line, { label: subtotalLabel, amount: money(preview.subtotal) }), preview.shippingFee > 0 && (_jsx(Line, { label: withStoreCount("Shipping", storeCount), amount: money(preview.shippingFee) })), preview.codHandlingFee > 0 && (_jsx(Line, { label: "COD handling fee", amount: money(preview.codHandlingFee) })), preview.
|
|
24
|
+
return (_jsxs(Stack, { gap: "xs", className: className, children: [_jsx(Line, { label: subtotalLabel, amount: money(preview.subtotal) }), preview.shippingFee > 0 && (_jsx(Line, { label: withStoreCount("Shipping", storeCount), amount: money(preview.shippingFee) })), preview.codHandlingFee > 0 && (_jsx(Line, { label: "COD handling fee", amount: money(preview.codHandlingFee) })), preview.giftWrapFee > 0 && (_jsx(Line, { label: withStoreCount("Gift wrap", countCharged((s) => s.giftWrapFee)), amount: money(preview.giftWrapFee) })), preview.shipmentProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Shipment protection", countCharged((s) => s.shipmentProtectionFee)), amount: money(preview.shipmentProtectionFee) })), preview.saleProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Sale protection", countCharged((s) => s.saleProtectionFee)), amount: money(preview.saleProtectionFee) })), preview.platformFee > 0 && _jsx(Line, { label: "Platform fee", amount: money(preview.platformFee) }), (preview.gstAmount > 0 || preview.gstOnFee > 0) && (_jsx(Line, { label: "GST", amount: money(preview.gstAmount + preview.gstOnFee) })), preview.couponDiscount > 0 && (_jsx(Line, { label: "Coupon discount", amount: `−${money(preview.couponDiscount)}`, tone: "success" })), _jsx(Div, { border: "top-subtle", paddingY: "t-sm", children: _jsx(Line, { label: "Total", amount: money(preview.total), tone: "primary", strong: true }) }), errorNote ? (_jsx(Text, { size: "xs", color: "error", children: errorNote })) : (isLoading && (_jsx(Text, { size: "xs", color: "muted", children: "Updating\u2026" })))] }));
|
|
25
25
|
}
|
|
@@ -3,13 +3,12 @@ import React from "react";
|
|
|
3
3
|
* Paid add-on checkboxes for ONE store.
|
|
4
4
|
*
|
|
5
5
|
* Add-on fees are billed per order group (= per store), so they are chosen per
|
|
6
|
-
* store: ticking "
|
|
6
|
+
* store: ticking "gift wrap" for one seller must not bill it for every
|
|
7
7
|
* seller in the cart. Rendered inside that store's card in the cart, and inside
|
|
8
8
|
* that store's row of the checkout summary — Buy Now skips the cart entirely,
|
|
9
9
|
* so both surfaces have to be able to edit them.
|
|
10
10
|
*/
|
|
11
11
|
export interface StoreAddonsValue {
|
|
12
|
-
whatsappNotifyAddon?: boolean;
|
|
13
12
|
giftWrapAddon?: boolean;
|
|
14
13
|
giftWrapMessage?: string;
|
|
15
14
|
shipmentProtectionAddon?: boolean;
|
|
@@ -17,8 +16,6 @@ export interface StoreAddonsValue {
|
|
|
17
16
|
}
|
|
18
17
|
/** The subset of `siteSettings.commissions` that gates and prices the add-ons. */
|
|
19
18
|
export interface StoreAddonsRates {
|
|
20
|
-
whatsappNotifyFeeEnabled?: boolean;
|
|
21
|
-
whatsappNotifyFee?: number;
|
|
22
19
|
giftWrapFeeEnabled?: boolean;
|
|
23
20
|
giftWrapFee?: number;
|
|
24
21
|
shipmentProtectionFeeEnabled?: boolean;
|
|
@@ -59,7 +56,7 @@ export interface StoreProtectionContext {
|
|
|
59
56
|
* showing an explanatory line instead.
|
|
60
57
|
*
|
|
61
58
|
* 🛑 Deliberately asks only about TIER 1. A seller who supports neither
|
|
62
|
-
* protection still has
|
|
59
|
+
* protection still has gift wrap, and a store whose only
|
|
63
60
|
* enabled add-ons are protections it declined must still render the picker —
|
|
64
61
|
* because that is where the risk notice lives, and a hidden notice is the
|
|
65
62
|
* tri-state failure in § `StoreProtectionContext`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Gift,
|
|
3
|
+
import { Gift, ShieldAlert, ShieldCheck, ShieldOff } from "lucide-react";
|
|
4
4
|
import { Div, Row, Stack, Text } from "../../../ui/index.js";
|
|
5
5
|
import { IconBox } from "../../../ui/components/IconBox.js";
|
|
6
6
|
import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox.js";
|
|
@@ -15,15 +15,14 @@ import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
|
15
15
|
* showing an explanatory line instead.
|
|
16
16
|
*
|
|
17
17
|
* 🛑 Deliberately asks only about TIER 1. A seller who supports neither
|
|
18
|
-
* protection still has
|
|
18
|
+
* protection still has gift wrap, and a store whose only
|
|
19
19
|
* enabled add-ons are protections it declined must still render the picker —
|
|
20
20
|
* because that is where the risk notice lives, and a hidden notice is the
|
|
21
21
|
* tri-state failure in § `StoreProtectionContext`.
|
|
22
22
|
*/
|
|
23
23
|
export function hasAnyStoreAddon(rates) {
|
|
24
24
|
return (!!rates &&
|
|
25
|
-
!!(rates.
|
|
26
|
-
rates.giftWrapFeeEnabled ||
|
|
25
|
+
!!(rates.giftWrapFeeEnabled ||
|
|
27
26
|
rates.shipmentProtectionFeeEnabled ||
|
|
28
27
|
rates.saleProtectionFeeEnabled));
|
|
29
28
|
}
|
|
@@ -51,7 +50,7 @@ export function StoreAddonsPicker({ storeId, storeSubtotal, value, onChange, rat
|
|
|
51
50
|
const shippingDeclined = !!rates.shipmentProtectionFeeEnabled && !protection?.supportsShippingProtection;
|
|
52
51
|
const saleDeclined = !!rates.saleProtectionFeeEnabled && !protection?.supportsSaleProtection;
|
|
53
52
|
const saleBlockedByFinalSale = !!rates.saleProtectionFeeEnabled && !!protection?.supportsSaleProtection && !!protection?.hasFinalSaleItem;
|
|
54
|
-
return (_jsxs(Stack, { gap: "xs", className: `min-w-0 ${className}`, children: [disabled && disabledReason && (_jsx(Text, { size: "xs", color: "muted", children: disabledReason })), rates.
|
|
53
|
+
return (_jsxs(Stack, { gap: "xs", className: `min-w-0 ${className}`, children: [disabled && disabledReason && (_jsx(Text, { size: "xs", color: "muted", children: disabledReason })), rates.giftWrapFeeEnabled && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(Gift, { size: 14 }) }), _jsxs(Stack, { gap: "xs", className: "flex-1 min-w-0", children: [_jsx(FieldCheckbox, { name: `giftWrapAddon-${storeId}`, label: `Gift wrap (+${money(rates.giftWrapFee ?? 49)})`, checked: !!value.giftWrapAddon, onChange: (checked) => set({ giftWrapAddon: checked }), disabled: disabled }), showGiftMessage && value.giftWrapAddon && (_jsx(FieldTextarea, { name: `giftWrapMessage-${storeId}`, label: "Gift message (optional)", value: value.giftWrapMessage ?? "", onChange: (next) => set({ giftWrapMessage: next.slice(0, 500) }), rows: 2, maxLength: 500, showCharCount: true, placeholder: "Add a note for the recipient\u2026", disabled: disabled }))] })] })), shippingOffered && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldCheck, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `shipmentProtectionAddon-${storeId}`, label: `Shipment protection — loss or damage in transit (+${money(protectionFee)})`, checked: !!value.shipmentProtectionAddon, onChange: (checked) => set({ shipmentProtectionAddon: checked }), disabled: disabled }) })] })), saleOffered && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldAlert, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `saleProtectionAddon-${storeId}`, label: `Sale protection — item not as described (+${money(saleFee)})`, checked: !!value.saleProtectionAddon, onChange: (checked) => set({ saleProtectionAddon: checked }), disabled: disabled }) })] })), (shippingDeclined || saleDeclined) && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "muted", children: _jsx(ShieldOff, { size: 14 }) }), _jsx(Text, { size: "xs", color: "muted", className: "flex-1 min-w-0", children: shippingDeclined && saleDeclined
|
|
55
54
|
? "This seller does not offer shipment or sale protection."
|
|
56
55
|
: shippingDeclined
|
|
57
56
|
? "This seller does not offer shipment protection."
|
|
@@ -8,9 +8,10 @@ import { CART_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
|
8
8
|
import { dispatchCartUpdated, formatCartAddedMessage } from "../utils/pending-ops.js";
|
|
9
9
|
import { useToast } from "../../../ui/components/Toast.js";
|
|
10
10
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
11
|
+
import { useCanSeePrices } from "../../../react/hooks/useCanSeePrices.js";
|
|
11
12
|
import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
|
|
12
|
-
function showAddedToast(showToast, detail) {
|
|
13
|
-
showToast(formatCartAddedMessage(detail, (amount) => formatCurrency(amount, getDefaultCurrency())), "success");
|
|
13
|
+
function showAddedToast(showToast, detail, canSeePrices) {
|
|
14
|
+
showToast(formatCartAddedMessage(detail, (amount) => formatCurrency(amount, getDefaultCurrency()), canSeePrices), "success");
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* useAddToCart
|
|
@@ -21,6 +22,10 @@ export function useAddToCart(options) {
|
|
|
21
22
|
const queryClient = useQueryClient();
|
|
22
23
|
const endpoint = options?.endpoint ?? CART_ENDPOINTS.GET;
|
|
23
24
|
const { showToast } = useToast();
|
|
25
|
+
// The guest fallback below fires precisely when the viewer is NOT signed in,
|
|
26
|
+
// so a toast that quoted the cart total was the one place the price gate was
|
|
27
|
+
// guaranteed to be speaking to a guest.
|
|
28
|
+
const { canSeePrices } = useCanSeePrices();
|
|
24
29
|
const optionsRef = useRef(options);
|
|
25
30
|
useEffect(() => {
|
|
26
31
|
optionsRef.current = options;
|
|
@@ -31,7 +36,7 @@ export function useAddToCart(options) {
|
|
|
31
36
|
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
32
37
|
const itemCount = data?.itemCount ?? 0;
|
|
33
38
|
const totalValue = data?.subtotal ?? 0;
|
|
34
|
-
showAddedToast(showToast, { productTitle: variables.productTitle, itemCount, totalValue });
|
|
39
|
+
showAddedToast(showToast, { productTitle: variables.productTitle, itemCount, totalValue }, canSeePrices);
|
|
35
40
|
dispatchCartUpdated({ itemCount, totalValue, productTitle: variables.productTitle });
|
|
36
41
|
optionsRef.current?.onSuccess?.();
|
|
37
42
|
},
|
|
@@ -55,14 +60,14 @@ export function useAddToCart(options) {
|
|
|
55
60
|
const guestItems = getGuestCartItems();
|
|
56
61
|
const itemCount = guestItems.reduce((sum, it) => sum + it.quantity, 0);
|
|
57
62
|
const totalValue = guestItems.reduce((sum, it) => sum + (it.price ?? 0) * it.quantity, 0);
|
|
58
|
-
showAddedToast(showToast, { productTitle: data.productTitle, itemCount, totalValue });
|
|
63
|
+
showAddedToast(showToast, { productTitle: data.productTitle, itemCount, totalValue }, canSeePrices);
|
|
59
64
|
dispatchCartUpdated({ itemCount, totalValue, productTitle: data.productTitle });
|
|
60
65
|
optionsRef.current?.onSuccess?.();
|
|
61
66
|
return undefined;
|
|
62
67
|
}
|
|
63
68
|
throw err;
|
|
64
69
|
}
|
|
65
|
-
}, [serverMutation, showToast]);
|
|
70
|
+
}, [serverMutation, showToast, canSeePrices]);
|
|
66
71
|
return {
|
|
67
72
|
mutate,
|
|
68
73
|
isLoading: serverMutation.isPending,
|
|
@@ -577,8 +577,7 @@ export class CartRepository extends BaseRepository {
|
|
|
577
577
|
*/
|
|
578
578
|
async setStoreAddons(userId, storeId, addons) {
|
|
579
579
|
const isEmpty = !addons ||
|
|
580
|
-
(!addons.
|
|
581
|
-
!addons.giftWrapAddon &&
|
|
580
|
+
(!addons.giftWrapAddon &&
|
|
582
581
|
!addons.shipmentProtectionAddon &&
|
|
583
582
|
!addons.saleProtectionAddon);
|
|
584
583
|
await this.db
|
|
@@ -202,12 +202,11 @@ export interface CartAppliedCoupon {
|
|
|
202
202
|
*
|
|
203
203
|
* Add-on fees have always been billed per order group (= per store) by
|
|
204
204
|
* `createOrderForGroup` / `previewCheckoutPricing`, but the selection used to be
|
|
205
|
-
* a single cart-wide boolean — so one tick on "
|
|
205
|
+
* a single cart-wide boolean — so one tick on "gift wrap" quietly billed
|
|
206
206
|
* ₹10 × every store in the cart, with no way to opt in for just one seller.
|
|
207
207
|
* Keying the selection by storeId makes the choice match the billing.
|
|
208
208
|
*/
|
|
209
209
|
export interface CartStoreAddons {
|
|
210
|
-
whatsappNotifyAddon?: boolean;
|
|
211
210
|
giftWrapAddon?: boolean;
|
|
212
211
|
giftWrapMessage?: string;
|
|
213
212
|
/** Loss or damage IN TRANSIT. */
|
|
@@ -30,7 +30,17 @@ export declare function dispatchCartUpdated(detail: CartUpdatedEventDetail): voi
|
|
|
30
30
|
* instead of a bare "Added to cart", so callers only need to format
|
|
31
31
|
* currency, not compose the sentence twice.
|
|
32
32
|
*/
|
|
33
|
-
export declare function formatCartAddedMessage(detail: CartUpdatedEventDetail, formatCurrency: (amount: number) => string
|
|
33
|
+
export declare function formatCartAddedMessage(detail: CartUpdatedEventDetail, formatCurrency: (amount: number) => string,
|
|
34
|
+
/**
|
|
35
|
+
* 🛑 A toast is a STRING, so it cannot be wrapped in `<GatedPrice>` — this is
|
|
36
|
+
* the third form of the guest-price gate, read as a value at the call site
|
|
37
|
+
* (CLAUDE.md § "Guest price gating"). Default `true` so no existing caller
|
|
38
|
+
* silently loses the total; the signed-out callers pass `false`.
|
|
39
|
+
*
|
|
40
|
+
* The COUNT is deliberately still shown. It is not money, and a guest who
|
|
41
|
+
* just added something is owed confirmation that it landed.
|
|
42
|
+
*/
|
|
43
|
+
includeTotal?: boolean): string;
|
|
34
44
|
export type CartOpKind = "add" | "remove";
|
|
35
45
|
export type WishlistOpKind = "add" | "remove";
|
|
36
46
|
export interface CartOp {
|
|
@@ -35,9 +35,21 @@ export function dispatchCartUpdated(detail) {
|
|
|
35
35
|
* instead of a bare "Added to cart", so callers only need to format
|
|
36
36
|
* currency, not compose the sentence twice.
|
|
37
37
|
*/
|
|
38
|
-
export function formatCartAddedMessage(detail, formatCurrency
|
|
38
|
+
export function formatCartAddedMessage(detail, formatCurrency,
|
|
39
|
+
/**
|
|
40
|
+
* 🛑 A toast is a STRING, so it cannot be wrapped in `<GatedPrice>` — this is
|
|
41
|
+
* the third form of the guest-price gate, read as a value at the call site
|
|
42
|
+
* (CLAUDE.md § "Guest price gating"). Default `true` so no existing caller
|
|
43
|
+
* silently loses the total; the signed-out callers pass `false`.
|
|
44
|
+
*
|
|
45
|
+
* The COUNT is deliberately still shown. It is not money, and a guest who
|
|
46
|
+
* just added something is owed confirmation that it landed.
|
|
47
|
+
*/
|
|
48
|
+
includeTotal = true) {
|
|
39
49
|
const itemLabel = detail.productTitle ? `"${detail.productTitle}"` : "Item";
|
|
40
|
-
|
|
50
|
+
const count = `${detail.itemCount} item${detail.itemCount !== 1 ? "s" : ""}`;
|
|
51
|
+
const total = includeTotal ? `, ${formatCurrency(detail.totalValue)} total` : "";
|
|
52
|
+
return `${itemLabel} added to cart — ${count}${total}`;
|
|
41
53
|
}
|
|
42
54
|
function dispatchCartOpsChange() {
|
|
43
55
|
if (typeof window === "undefined")
|
|
@@ -43,30 +43,7 @@ import { getProviders } from "../../../../../contracts/index.js";
|
|
|
43
43
|
import { categoriesRepository } from "../../../repository/categories.repository.js";
|
|
44
44
|
import { isAdminUser } from "../../../../auth/role-predicates.js";
|
|
45
45
|
import { priceGuidanceOnSellerBasis, describePriceGuidanceBasis, } from "../../../../../_internal/shared/features/categories/price-guidance.js";
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Nearest-first ancestor walk for `priceGuidance`, same shape as
|
|
49
|
-
* `description-template/route.ts`'s `resolveInherited` — the leaf's own band
|
|
50
|
-
* wins, and only a leaf with none falls back to the nearest ancestor that has
|
|
51
|
-
* one. ONE bounded `findByIds`, not a sequential walk: a seller form hitting
|
|
52
|
-
* this on every category change should not pay up to eight round trips for it.
|
|
53
|
-
*/
|
|
54
|
-
async function resolveInheritedGuidance(leaf) {
|
|
55
|
-
if (leaf.productDefaults?.priceGuidance)
|
|
56
|
-
return leaf.productDefaults.priceGuidance;
|
|
57
|
-
const parentIds = (leaf.parentIds ?? []).slice(0, MAX_ANCESTORS);
|
|
58
|
-
if (parentIds.length === 0)
|
|
59
|
-
return undefined;
|
|
60
|
-
const nearestFirst = [...parentIds].reverse();
|
|
61
|
-
const ancestors = await categoriesRepository.findByIds(nearestFirst);
|
|
62
|
-
const byId = new Map(ancestors.map((a) => [a.id, a]));
|
|
63
|
-
for (const pid of nearestFirst) {
|
|
64
|
-
const guidance = byId.get(pid)?.productDefaults?.priceGuidance;
|
|
65
|
-
if (guidance)
|
|
66
|
-
return guidance;
|
|
67
|
-
}
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
46
|
+
import { resolveInheritedGuidance, } from "../../../../../_internal/server/features/categories/price-guidance-resolve.js";
|
|
70
47
|
export const GET = createRouteHandler({
|
|
71
48
|
roles: ["seller", "admin", "moderator"],
|
|
72
49
|
handler: async ({ user, params }) => {
|
|
@@ -5,6 +5,7 @@ import Link from "next/link.js";
|
|
|
5
5
|
import { categoriesRepository, productRepository, } from "../../../repositories/index.js";
|
|
6
6
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
7
7
|
import { Anchor, Container, Div, Dl, Dt, Dd, Heading, Main, Nav, Section, Span, Stack, Text } from "../../../ui/index.js";
|
|
8
|
+
import { OwnerQuickActions } from "../../admin/components/OwnerQuickActions.js";
|
|
8
9
|
import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv.js";
|
|
9
10
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
10
11
|
import { RichTextRenderer } from "../../../ui/rich-text/RichTextRenderer.js";
|
|
@@ -60,7 +61,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
60
61
|
// therefore never hide, however empty the brand was.
|
|
61
62
|
brandName
|
|
62
63
|
? listingTabCounts(CATEGORY_PAGE_TABS, { brandName })
|
|
63
|
-
: Promise.resolve({}),
|
|
64
|
+
: Promise.resolve({ available: {}, total: {} }),
|
|
64
65
|
// SB-UNI-D — bundles are categoryType:"bundle" rows on the categories
|
|
65
66
|
// collection, tagged to a brand via the real brandSlug field (added
|
|
66
67
|
// 2026-08-21 — previously a fragile seo.keywords string-match heuristic).
|
|
@@ -108,15 +109,23 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
108
109
|
// Keyed by tabSlug. `bundles` is overridden with the brand-scoped figure —
|
|
109
110
|
// `listingTabCounts` cannot narrow bundles by brand (a bundle's brand lives on
|
|
110
111
|
// `brandSlug`, which this page already filters on locally).
|
|
112
|
+
// Badge = available scope; hiding + the header pill = total. See the same
|
|
113
|
+
// split in `CategoryDetailPageView` and `ListingTabCountResult`.
|
|
111
114
|
const counts = {
|
|
112
|
-
...tabCounts,
|
|
113
|
-
products: tabCounts.products ?? brand?.metrics?.productCount ?? 0,
|
|
114
|
-
auctions: tabCounts.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
115
|
+
...tabCounts.available,
|
|
116
|
+
products: tabCounts.available.products ?? brand?.metrics?.productCount ?? 0,
|
|
117
|
+
auctions: tabCounts.available.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
118
|
+
bundles: brandBundles.length,
|
|
119
|
+
};
|
|
120
|
+
const totals = {
|
|
121
|
+
...tabCounts.total,
|
|
122
|
+
products: tabCounts.total.products ?? brand?.metrics?.productCount ?? 0,
|
|
123
|
+
auctions: tabCounts.total.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
115
124
|
bundles: brandBundles.length,
|
|
116
125
|
};
|
|
117
126
|
// Header total — `?? 0` is right here (a pill just doesn't render), unlike the
|
|
118
127
|
// tab bar where an unknown count must keep its tab visible.
|
|
119
|
-
const totalItems = Object.values(
|
|
128
|
+
const totalItems = Object.values(totals).reduce((sum, n) => sum + (n ?? 0), 0);
|
|
120
129
|
/*
|
|
121
130
|
* ── D4: structured data on the brand page ───────────────────────────────
|
|
122
131
|
*
|
|
@@ -168,5 +177,5 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
168
177
|
const ldFaq = brand?.faqs && brand.faqs.length > 0
|
|
169
178
|
? faqJsonLd(brand.faqs.map((f) => ({ question: f.question, answer: f.answer })))
|
|
170
179
|
: null;
|
|
171
|
-
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), brandContentBody && brandContentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: brandContentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
180
|
+
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [brand?.id && (_jsx(OwnerQuickActions, { resource: "BRAND", placement: "detail", className: "mb-2", recordLabel: brand.name ?? slug, editHref: String(ROUTES.ADMIN.BRANDS_EDIT(brand.id)) })), _jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), brandContentBody && brandContentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: brandContentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, totals: totals, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
172
181
|
}
|
|
@@ -10,10 +10,16 @@ export interface BrandDetailTabsProps {
|
|
|
10
10
|
* inventory (Root Cause #59). Keyed by tab id; the previous hand-named shape
|
|
11
11
|
* covered only 5 of 9 tabs (Root Cause #61).
|
|
12
12
|
*/
|
|
13
|
+
/** Badge counts — what the default (available) scope will render. */
|
|
13
14
|
counts?: Record<string, number | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Every published row per tab, all availability scopes. Drives hide-at-zero
|
|
17
|
+
* and the "N ended — show all" hint; see `ListingTabCountResult`.
|
|
18
|
+
*/
|
|
19
|
+
totals?: Record<string, number | undefined>;
|
|
14
20
|
/** Enabled listing types (e.g. ["standard","auction"]). When omitted, all tabs shown. */
|
|
15
21
|
enabledListingTypes?: string[];
|
|
16
22
|
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
17
23
|
enabledCategoryTypes?: string[];
|
|
18
24
|
}
|
|
19
|
-
export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): React.JSX.Element;
|
|
25
|
+
export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, totals, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): React.JSX.Element;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { EmptyState, Tabs, TabsList, TabsTrigger } from "../../../ui/index.js";
|
|
5
|
+
import { AvailabilityScopeHint } from "../../products/components/AvailabilityScopeHint.js";
|
|
5
6
|
import { CategoryProductsListing } from "./CategoryProductsListing.js";
|
|
6
7
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing.js";
|
|
7
8
|
import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing.js";
|
|
@@ -28,6 +29,18 @@ const TAB_TYPE_MAP = {
|
|
|
28
29
|
])),
|
|
29
30
|
bundles: { kind: "category", type: "bundle" },
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* The listing types a tab spans, for the availability-scope hint. Derived
|
|
34
|
+
* from the same maps the tabs render from, never hand-written (Root Cause
|
|
35
|
+
* #61).
|
|
36
|
+
*/
|
|
37
|
+
function typesForTab(id) {
|
|
38
|
+
const generic = GENERIC_TAB_LISTING_TYPES[id];
|
|
39
|
+
if (generic)
|
|
40
|
+
return generic;
|
|
41
|
+
const mapping = TAB_TYPE_MAP[id];
|
|
42
|
+
return mapping?.kind === "listing" ? [mapping.type] : [];
|
|
43
|
+
}
|
|
31
44
|
/** Listing types rendered by the shared products listing rather than a dedicated one. */
|
|
32
45
|
const GENERIC_TAB_LISTING_TYPES = {
|
|
33
46
|
[pluginFor("standard").tabSlug]: ["standard"],
|
|
@@ -46,8 +59,9 @@ const GENERIC_TAB_LISTING_TYPES = {
|
|
|
46
59
|
*/
|
|
47
60
|
[pluginFor("spares").tabSlug]: ["spares"],
|
|
48
61
|
};
|
|
49
|
-
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
62
|
+
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, totals, enabledListingTypes, enabledCategoryTypes, }) {
|
|
50
63
|
const countFor = (id) => counts?.[id];
|
|
64
|
+
const totalFor = (id) => totals?.[id] ?? counts?.[id];
|
|
51
65
|
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
52
66
|
const mapping = TAB_TYPE_MAP[t.id];
|
|
53
67
|
// A tab with no mapping has no render branch below and would show a blank
|
|
@@ -67,7 +81,8 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
67
81
|
// Hide a tab only when its count is known and explicitly zero. `undefined`
|
|
68
82
|
// now means the count query FAILED (every tab is counted), so the tab stays
|
|
69
83
|
// visible rather than hiding real listings behind a swallowed error.
|
|
70
|
-
|
|
84
|
+
// TOTAL, not the badge — see the same note in CategoryDetailTabs.
|
|
85
|
+
const count = totalFor(t.id);
|
|
71
86
|
return count === undefined || count > 0;
|
|
72
87
|
});
|
|
73
88
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
@@ -75,5 +90,5 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
75
90
|
if (visibleTabs.length === 0) {
|
|
76
91
|
return (_jsx(EmptyState, { title: "Nothing listed here yet", description: "This brand has no active listings right now. Check back soon." }));
|
|
77
92
|
}
|
|
78
|
-
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
|
|
93
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), _jsx(AvailabilityScopeHint, { total: totalFor(activeTab), available: countFor(activeTab), types: typesForTab(activeTab) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
|
|
79
94
|
}
|
|
@@ -16,10 +16,11 @@ import { MediaImage } from "../../media/MediaImage.js";
|
|
|
16
16
|
import { ImageLightbox } from "../../../ui/components/ImageLightbox.js";
|
|
17
17
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
18
18
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
19
|
+
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
19
20
|
const PLACEHOLDER_EMOJI = "📦";
|
|
20
21
|
function toGalleryImages(members, canSeePrices) {
|
|
21
22
|
return members.map((p, i) => ({
|
|
22
|
-
src: p.mainImage
|
|
23
|
+
src: p.mainImage || productImageUrls(p.images)[0] || "",
|
|
23
24
|
alt: p.title,
|
|
24
25
|
badge: `#${i + 1}`,
|
|
25
26
|
caption: p.title,
|
|
@@ -46,7 +47,7 @@ export function BundleCollage({ members, onItemClick }) {
|
|
|
46
47
|
return null;
|
|
47
48
|
const galleryImages = toGalleryImages(members, canSeePrices);
|
|
48
49
|
return (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: "categoryCards", gap: "3", children: members.map((p, idx) => {
|
|
49
|
-
const cover = p.mainImage
|
|
50
|
+
const cover = p.mainImage || productImageUrls(p.images)[0];
|
|
50
51
|
const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
|
|
51
52
|
return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: _jsx(GatedPrice, { children: formatCurrency(p.price ?? 0, p.currency ?? "INR") }) })] })] }, p.id));
|
|
52
53
|
}) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
|
|
@@ -9,6 +9,7 @@ import { MediaImage } from "../../media/MediaImage.js";
|
|
|
9
9
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
10
10
|
import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing.js";
|
|
11
11
|
import { CategoryGrid } from "./CategoryGrid.js";
|
|
12
|
+
import { OwnerQuickActions } from "../../../features/admin/components/OwnerQuickActions.js";
|
|
12
13
|
const __O = {
|
|
13
14
|
hidden: "overflow-hidden",
|
|
14
15
|
};
|
|
@@ -25,7 +26,7 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, onAddToCart,
|
|
|
25
26
|
? formatCurrency(bundle.bundlePrice, "INR")
|
|
26
27
|
: BUNDLE_COPY.detail.priceFallback;
|
|
27
28
|
const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
|
|
28
|
-
return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: priceLabel }) }), discount && (
|
|
29
|
+
return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(OwnerQuickActions, { resource: "BUNDLE", placement: "detail", recordLabel: String(bundle.name), editHref: String(ROUTES.ADMIN.BUNDLES_EDIT(String(bundle.id))) }), _jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: priceLabel }) }), discount && (
|
|
29
30
|
// Both the struck-through total and the "N% off" badge are
|
|
30
31
|
// derived from the bundle price, so they disclose it even
|
|
31
32
|
// though the badge carries no currency symbol.
|
|
@@ -18,6 +18,7 @@ import { Badge, Button, Checkbox, Div, Input, Row, Stack, Text } from "../../../
|
|
|
18
18
|
import { BUNDLE_MAX_ITEMS, BUNDLE_MIN_ITEMS, } from "../../../_internal/shared/features/categories/bundle-config.js";
|
|
19
19
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy.js";
|
|
20
20
|
import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
21
|
+
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
21
22
|
const __P = {
|
|
22
23
|
p3: "p-[var(--appkit-space-3)]",
|
|
23
24
|
};
|
|
@@ -137,11 +138,9 @@ export async function defaultBundleItemsFetch(query) {
|
|
|
137
138
|
return {
|
|
138
139
|
id,
|
|
139
140
|
title,
|
|
140
|
-
image: typeof rec.mainImage === "string"
|
|
141
|
+
image: typeof rec.mainImage === "string" && rec.mainImage
|
|
141
142
|
? rec.mainImage
|
|
142
|
-
:
|
|
143
|
-
? rec.images[0]
|
|
144
|
-
: undefined,
|
|
143
|
+
: productImageUrls(rec.images)[0],
|
|
145
144
|
price: typeof rec.price === "number" ? rec.price : undefined,
|
|
146
145
|
storeId: typeof rec.storeId === "string" ? rec.storeId : undefined,
|
|
147
146
|
};
|