@mohasinac/appkit 4.9.5 → 4.11.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/layout/DashboardLayoutClient.d.ts +6 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
- package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
- package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
- package/dist/_internal/server/features/auctions/actions.js +3 -2
- package/dist/_internal/server/features/auctions/service.d.ts +4 -3
- package/dist/_internal/server/features/auctions/service.js +6 -6
- package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
- package/dist/_internal/server/features/audit-log/actions.js +38 -0
- package/dist/_internal/server/features/brands/actions.js +0 -2
- package/dist/_internal/server/features/brands/og.d.ts +4 -1
- package/dist/_internal/server/features/brands/og.js +1 -1
- package/dist/_internal/server/features/bundles/data.d.ts +9 -0
- package/dist/_internal/server/features/bundles/data.js +14 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/catalogue/og.d.ts +20 -0
- package/dist/_internal/server/features/catalogue/og.js +27 -0
- package/dist/_internal/server/features/grouped/data.d.ts +16 -0
- package/dist/_internal/server/features/grouped/data.js +50 -1
- package/dist/_internal/server/features/grouped/index.d.ts +1 -1
- package/dist/_internal/server/features/grouped/index.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +9 -0
- package/dist/_internal/server/features/orders/adapters.js +25 -0
- package/dist/_internal/server/features/products/data.d.ts +23 -0
- package/dist/_internal/server/features/products/data.js +159 -0
- package/dist/_internal/server/features/products/index.d.ts +1 -1
- package/dist/_internal/server/features/products/index.js +1 -1
- package/dist/_internal/server/functions/scheduled.d.ts +3 -1
- package/dist/_internal/server/functions/scheduled.js +17 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
- package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/index.js +3 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
- package/dist/_internal/server/notifications/channel-health.js +101 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +20 -0
- package/dist/_internal/shared/features/auctions/config.js +31 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
- package/dist/_internal/shared/features/brands/schema.js +0 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/art/config.js +1 -1
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/classified/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/stickers/config.js +1 -1
- package/dist/client.d.ts +21 -12
- package/dist/client.js +14 -7
- package/dist/constants/api-endpoints.d.ts +9 -9
- package/dist/constants/api-endpoints.js +3 -3
- package/dist/contracts/client-auth.d.ts +3 -7
- package/dist/contracts/email.d.ts +1 -0
- package/dist/features/account/components/AddressForm.js +24 -2
- package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
- package/dist/features/account/components/BecomeSellerView.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserBidsView.d.ts +1 -0
- package/dist/features/account/components/UserBidsView.js +47 -0
- package/dist/features/account/components/UserOrdersView.d.ts +4 -11
- package/dist/features/account/components/UserOrdersView.js +54 -4
- package/dist/features/account/components/UserReturnsView.d.ts +4 -9
- package/dist/features/account/components/UserReturnsView.js +37 -3
- package/dist/features/account/components/index.d.ts +3 -2
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/admin/actions/admin-actions.js +22 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
- package/dist/features/admin/actions/notification-actions.js +4 -3
- package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
- package/dist/features/admin/components/AdminAdsView.js +86 -89
- package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
- package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
- package/dist/features/admin/components/AdminAuditLogView.js +95 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
- package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
- package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
- package/dist/features/admin/components/AdminCartsView.js +34 -4
- package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminHistoryView.js +11 -1
- package/dist/features/admin/components/AdminNotificationsView.js +27 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +92 -24
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
- package/dist/features/admin/components/AdminPayoutsView.js +15 -31
- package/dist/features/admin/components/AdminProductEditorView.js +26 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +31 -4
- package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
- package/dist/features/admin/components/AdminStoresView.js +7 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
- package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
- package/dist/features/admin/components/AdminUserEditorView.js +4 -2
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +14 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -2
- package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
- package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
- package/dist/features/admin/components/ViewNotificationModal.js +15 -0
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
- package/dist/features/admin/components/index.d.ts +8 -1
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
- package/dist/features/admin/constants/filter-tabs.js +27 -0
- package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
- package/dist/features/admin/hooks/useAdminListing.js +3 -1
- package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
- package/dist/features/admin/hooks/useAdminListingData.js +9 -2
- package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.d.ts +21 -0
- package/dist/features/admin/schemas/firestore.js +6 -0
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +8 -0
- package/dist/features/auctions/actions/bid-actions.js +4 -1
- package/dist/features/auctions/components/AuctionBottomActions.d.ts +2 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +14 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +21 -9
- package/dist/features/auctions/components/LiveMinIncrement.d.ts +18 -0
- package/dist/features/auctions/components/LiveMinIncrement.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +5 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +7 -3
- package/dist/features/auctions/schemas/index.d.ts +5 -0
- package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
- package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
- package/dist/features/audit-log/schemas/firestore.js +35 -0
- package/dist/features/auth/actions/index.d.ts +0 -1
- package/dist/features/auth/actions/index.js +0 -1
- package/dist/features/auth/hooks/useAuth.d.ts +8 -35
- package/dist/features/auth/hooks/useAuth.js +21 -37
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +2 -0
- package/dist/features/blog/schemas/index.d.ts +32 -0
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.d.ts +11 -0
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +33 -0
- package/dist/features/catalogue/components/PublicCatalogueView.js +2 -1
- package/dist/features/catalogue/index.d.ts +2 -0
- package/dist/features/catalogue/index.js +1 -0
- package/dist/features/categories/components/BrandDetailPageView.js +14 -12
- package/dist/features/categories/components/BundleDetailView.d.ts +3 -1
- package/dist/features/categories/components/BundleDetailView.js +3 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
- package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
- package/dist/features/categories/components/CategoryProductsView.js +1 -1
- package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
- package/dist/features/categories/schemas/firestore.d.ts +19 -2
- package/dist/features/categories/schemas/index.d.ts +24 -3
- package/dist/features/categories/schemas/index.js +3 -1
- package/dist/features/categories/types/index.d.ts +7 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
- package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +12 -0
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +49 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +7 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.js +69 -0
- package/dist/features/contact/components/ContactForm.js +17 -4
- package/dist/features/contact/components/ContactPageView.js +8 -2
- package/dist/features/contact/email.d.ts +1 -0
- package/dist/features/contact/email.js +3 -0
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.d.ts +19 -0
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +79 -0
- package/dist/features/events/components/AdminEventEditorView.js +54 -2
- package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
- package/dist/features/events/components/AdminEventEntriesView.js +56 -89
- package/dist/features/events/components/EventFormDrawer.js +5 -1
- package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
- package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
- package/dist/features/events/schemas/index.d.ts +22 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.d.ts +13 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.js +31 -0
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +12 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +34 -0
- package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
- package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/layout/BottomActions.js +4 -3
- package/dist/features/layout/BottomActionsContext.d.ts +10 -0
- package/dist/features/layout/BottomActionsContext.js +5 -1
- package/dist/features/layout/TitleBarLayout.d.ts +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/layout/hooks/useBottomActions.d.ts +2 -0
- package/dist/features/layout/hooks/useBottomActions.js +4 -1
- package/dist/features/live/components/LiveItemDetailPageView.d.ts +9 -0
- package/dist/features/live/components/LiveItemDetailPageView.js +74 -0
- package/dist/features/media/MediaImage.js +39 -3
- package/dist/features/media/MediaVideo.js +17 -1
- package/dist/features/media/types/index.d.ts +10 -0
- package/dist/features/media/types/index.js +1 -0
- package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
- package/dist/features/media/upload/MediaUploadField.js +11 -7
- package/dist/features/media/upload/video-poster.d.ts +2 -0
- package/dist/features/media/upload/video-poster.js +16 -1
- package/dist/features/media/upload/video-poster.test.js +1 -1
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
- package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/constants/payment-window.d.ts +25 -0
- package/dist/features/orders/constants/payment-window.js +25 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +56 -0
- package/dist/features/orders/schemas/index.d.ts +24 -24
- package/dist/features/orders/types/index.d.ts +33 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
- package/dist/features/pre-orders/schemas/index.d.ts +5 -0
- package/dist/features/products/components/BidHistory.js +9 -1
- package/dist/features/products/components/GroupSettingsPanel.js +25 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +11 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
- package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
- package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
- package/dist/features/products/components/ProductDetailActions.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +11 -116
- package/dist/features/products/components/ProductGrid.js +15 -1
- package/dist/features/products/components/RelatedItemsSection.d.ts +16 -0
- package/dist/features/products/components/RelatedItemsSection.js +17 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/index.d.ts +5 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/search/components/SearchView.d.ts +1 -3
- package/dist/features/search/components/SearchView.js +1 -1
- package/dist/features/seller/components/QuickProductForm.js +2 -1
- package/dist/features/seller/components/SellerBidsView.js +66 -107
- package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
- package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
- package/dist/features/seller/components/SellerOrdersView.js +134 -163
- package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
- package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
- package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
- package/dist/features/seller/components/SellerPayoutsView.js +25 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
- package/dist/features/seller/components/SellerProductShell.js +90 -37
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
- package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
- package/dist/features/seller/components/SellerProductsView.js +131 -189
- package/dist/features/seller/components/SellerReviewsView.js +95 -133
- package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
- package/dist/features/seller/components/SellerShippingView.js +41 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
- package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
- package/dist/features/seller/components/SellerStorefrontView.js +42 -5
- package/dist/features/seller/components/index.d.ts +2 -2
- package/dist/features/seller/components/index.js +2 -2
- package/dist/features/shell/FormShell.d.ts +24 -4
- package/dist/features/shell/FormShell.js +12 -6
- package/dist/features/shell/QuickFormDrawer.js +67 -12
- package/dist/features/shell/StepForm.d.ts +30 -1
- package/dist/features/shell/StepForm.js +47 -4
- package/dist/features/shell/index.d.ts +1 -1
- package/dist/features/shell/index.js +1 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
- package/dist/features/shipments/schemas/validation.d.ts +14 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
- package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
- package/dist/features/tester/components/TesterHubView.js +3 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
- package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +487 -18
- package/dist/index.d.ts +17 -3
- package/dist/index.js +23 -5
- package/dist/next/routing/route-map.d.ts +13 -0
- package/dist/next/routing/route-map.js +12 -0
- package/dist/providers/email-resend/provider.js +1 -0
- package/dist/providers/firebase-client/auth.d.ts +1 -0
- package/dist/providers/firebase-client/auth.js +7 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +61 -25
- package/dist/security/pii-mask.js +1 -1
- package/dist/seed/bids-seed-data.js +48 -3
- package/dist/seed/categories-seed-data.js +69 -0
- package/dist/seed/grouped-listings-seed-data.js +167 -0
- package/dist/seed/payouts-seed-data.js +4 -4
- package/dist/seed/products-art-seed-data.js +188 -5
- package/dist/seed/products-auctions-seed-data.js +226 -6
- package/dist/seed/products-classifieds-seed-data.js +255 -8
- package/dist/seed/products-digital-codes-seed-data.js +258 -19
- package/dist/seed/products-live-items-seed-data.js +143 -10
- package/dist/seed/products-preorders-seed-data.js +213 -8
- package/dist/seed/products-prize-draws-seed-data.js +198 -0
- package/dist/seed/products-standard-seed-data.js +234 -4
- package/dist/seed/products-stickers-seed-data.js +178 -5
- package/dist/seed/site-settings-seed-data.js +23 -30
- package/dist/seed/store-extensions-seed-data.js +2 -2
- package/dist/server-entry.d.ts +6 -2
- package/dist/server-entry.js +24 -3
- package/dist/server.d.ts +10 -3
- package/dist/server.js +11 -4
- package/dist/styles.css +29 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +11 -2
- package/dist/ui/components/Form.js +4 -3
- package/dist/ui/components/FormField.d.ts +3 -1
- package/dist/ui/components/FormField.js +22 -4
- package/dist/ui/components/ImageLightbox.js +37 -3
- package/dist/ui/components/ImageLightbox.style.css +28 -0
- package/dist/ui/components/ListingLayout.d.ts +1 -2
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/components/RecordDetailModal.d.ts +68 -0
- package/dist/ui/components/RecordDetailModal.js +8 -0
- package/dist/ui/components/SlottedListingView.js +9 -1
- package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
- package/dist/ui/forms/FormErrorSummary.js +37 -0
- package/dist/ui/forms/FormShell.d.ts +37 -2
- package/dist/ui/forms/FormShell.js +37 -7
- package/dist/ui/forms/FormShell.style.css +17 -0
- package/dist/ui/forms/index.d.ts +2 -0
- package/dist/ui/forms/index.js +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +5 -0
- package/dist/utils/media-url.d.ts +12 -1
- package/dist/utils/media-url.js +59 -1
- package/dist/validation/schemas.d.ts +4 -4
- package/package.json +1 -1
- package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
- package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
- package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
- package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
- package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
- package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
- package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
- package/dist/features/auth/consent-otp.d.ts +0 -57
- package/dist/features/auth/consent-otp.js +0 -127
- package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
- package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
- package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
- package/dist/features/checkout/actions/checkout-actions.js +0 -124
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
- package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
- package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
- package/dist/features/classified/components/ClassifiedListView.js +0 -44
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
- package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
- package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
- package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
- package/dist/features/live/components/LiveItemsListView.js +0 -44
- package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
- package/dist/features/seller/components/SellerTemplatesView.js +0 -276
- package/dist/react/hooks/useVisibleItems.d.ts +0 -37
- package/dist/react/hooks/useVisibleItems.js +0 -59
- package/dist/ui/components/TabStrip.d.ts +0 -34
- package/dist/ui/components/TabStrip.js +0 -56
- package/dist/ui/components/TabStrip.style.css +0 -97
|
@@ -84,6 +84,9 @@ export function BottomActionsProvider({ children, }) {
|
|
|
84
84
|
const setInfoLabel = useCallback((infoLabel) => {
|
|
85
85
|
setState((prev) => ({ ...prev, infoLabel }));
|
|
86
86
|
}, []);
|
|
87
|
+
const setSecondaryLabel = useCallback((secondaryLabel) => {
|
|
88
|
+
setState((prev) => ({ ...prev, secondaryLabel }));
|
|
89
|
+
}, []);
|
|
87
90
|
const clearAll = useCallback(() => {
|
|
88
91
|
actionCallbacksRef.current = new Map();
|
|
89
92
|
bulkCallbacksRef.current = new Map();
|
|
@@ -98,8 +101,9 @@ export function BottomActionsProvider({ children, }) {
|
|
|
98
101
|
bulkCallbacksRef,
|
|
99
102
|
bulkClearRef,
|
|
100
103
|
setInfoLabel,
|
|
104
|
+
setSecondaryLabel,
|
|
101
105
|
clearAll,
|
|
102
|
-
}), [state, setActions, setBulkConfig, setInfoLabel, clearAll]);
|
|
106
|
+
}), [state, setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, clearAll]);
|
|
103
107
|
return (_jsx(BottomActionsContext.Provider, { value: value, children: children }));
|
|
104
108
|
}
|
|
105
109
|
// --- Hook ---------------------------------------------------------------------
|
|
@@ -66,4 +66,4 @@ export interface TitleBarLayoutProps {
|
|
|
66
66
|
*
|
|
67
67
|
* Receives all domain data as props — zero domain imports.
|
|
68
68
|
*/
|
|
69
|
-
export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen
|
|
69
|
+
export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
|
|
@@ -14,7 +14,7 @@ const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-cente
|
|
|
14
14
|
*
|
|
15
15
|
* Receives all domain data as props — zero domain imports.
|
|
16
16
|
*/
|
|
17
|
-
export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen
|
|
17
|
+
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 = "", }) {
|
|
18
18
|
// ── Element builders ────────────────────────────────────────────────────────
|
|
19
19
|
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-200 dark:hover:bg-secondary-900/60 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;
|
|
20
20
|
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;
|
|
@@ -22,7 +22,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
22
22
|
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;
|
|
23
23
|
// Compare is always lg+ (desktop-only feature, less critical on mobile)
|
|
24
24
|
const compareEl = compareHref ? (_jsx(Link, { href: compareHref, "aria-label": "Compare items", className: `${iconBtn} hidden lg:flex`, 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: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" }) }) })) : null;
|
|
25
|
-
const searchBtn = (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Search", onClick: onSearchToggle, className: iconBtn, "data-tour": "nav-search", 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: "M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" }) }) }));
|
|
25
|
+
const searchBtn = (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Search", "aria-pressed": searchOpen, onClick: onSearchToggle, className: iconBtn, "data-tour": "nav-search", 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: "M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" }) }) }));
|
|
26
26
|
// Dedicated notification bell — rendered immediately before the wishlist
|
|
27
27
|
// icon (per layout request). Falls back to invisible when no href is set;
|
|
28
28
|
// the count badge mirrors the wishlist/cart pattern.
|
|
@@ -33,7 +33,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
33
33
|
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
34
34
|
const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
|
|
35
35
|
// ── Render ───────────────────────────────────────────────────────────────────
|
|
36
|
-
return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _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: 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", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
|
|
36
|
+
return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _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", 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
|
|
37
37
|
? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
|
|
38
38
|
: authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, tourBtn, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { border: "subtle", as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t px-[var(--appkit-space-1)]", children: [notificationsEl, wishlistEl, cartEl, profileEl] }))] })] }));
|
|
39
39
|
}
|
|
@@ -3,5 +3,7 @@ export interface UseBottomActionsOptions {
|
|
|
3
3
|
actions?: BottomAction[];
|
|
4
4
|
bulk?: BottomBulkConfig;
|
|
5
5
|
infoLabel?: string;
|
|
6
|
+
/** Rendered as its own row above infoLabel. See BottomActionsState.secondaryLabel. */
|
|
7
|
+
secondaryLabel?: string;
|
|
6
8
|
}
|
|
7
9
|
export declare function useBottomActions(options?: UseBottomActionsOptions): void;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { useBottomActionsContext, } from "../BottomActionsContext";
|
|
4
4
|
export function useBottomActions(options = {}) {
|
|
5
|
-
const { setActions, setBulkConfig, setInfoLabel, clearAll } = useBottomActionsContext();
|
|
5
|
+
const { setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, clearAll } = useBottomActionsContext();
|
|
6
6
|
const actionsRef = useRef(options.actions ?? []);
|
|
7
7
|
actionsRef.current = options.actions ?? [];
|
|
8
8
|
const bulkRef = useRef(options.bulk);
|
|
@@ -22,6 +22,9 @@ export function useBottomActions(options = {}) {
|
|
|
22
22
|
useEffect(() => {
|
|
23
23
|
setInfoLabel(options.infoLabel);
|
|
24
24
|
}, [setInfoLabel, options.infoLabel]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
setSecondaryLabel(options.secondaryLabel);
|
|
27
|
+
}, [setSecondaryLabel, options.secondaryLabel]);
|
|
25
28
|
useEffect(() => {
|
|
26
29
|
return () => clearAll();
|
|
27
30
|
}, [clearAll]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProductDocument } from "../../products/schemas/firestore";
|
|
2
|
+
export interface LiveItemDetailPageViewProps {
|
|
3
|
+
slug: string;
|
|
4
|
+
/** Pre-fetched product document — dedupes with generateMetadata() via React.cache(). */
|
|
5
|
+
initialProduct?: ProductDocument | null;
|
|
6
|
+
/** Render-prop for the add-to-cart CTA — wired by the page shim (carries the vendor-verification gate). */
|
|
7
|
+
renderActions?: (product: ProductDocument) => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export declare function LiveItemDetailPageView({ slug, initialProduct, renderActions }: LiveItemDetailPageViewProps): Promise<import("react").JSX.Element>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { getLiveItemForDetail } from "../../../_internal/server/features/live/data";
|
|
4
|
+
const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
|
|
5
|
+
const CLS_LIVE_ITEM_BADGE = "inline-block rounded-full bg-lime-100 dark:bg-lime-900/30 px-[var(--appkit-space-2-5)] py-[var(--appkit-space-0-5)] text-lime-700 dark:text-lime-300";
|
|
6
|
+
const CLS_WARN_BOX = "rounded-lg border border-warning bg-warning-surface p-[var(--appkit-space-4)] text-[length:var(--appkit-text-sm)]";
|
|
7
|
+
const CLS_WARN_TITLE = "font-medium text-warning";
|
|
8
|
+
const CLS_WARN_BODY = "mt-1 text-warning";
|
|
9
|
+
import { ROUTES } from "../../../next";
|
|
10
|
+
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
11
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
12
|
+
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
13
|
+
import { safeDisplayName } from "../../../security";
|
|
14
|
+
import { Div, Container, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
|
|
15
|
+
import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
|
|
16
|
+
import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
|
|
17
|
+
import { ProductTabsShell } from "../../products/components/ProductTabsShell";
|
|
18
|
+
import { CustomSectionTabContent } from "../../products/components/CustomSectionTabContent";
|
|
19
|
+
import { ShareButton } from "../../products/components/ShareButton";
|
|
20
|
+
import { HistoryTracker } from "../../history/components/HistoryTracker";
|
|
21
|
+
import { RelatedItemsSection } from "../../products/components/RelatedItemsSection";
|
|
22
|
+
import { computeRelatedItems, getReviewItemsForProduct } from "../../../_internal/server/features/products/data";
|
|
23
|
+
import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
|
|
24
|
+
import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
|
|
25
|
+
import { ReviewsList } from "../../reviews/components/ReviewsList";
|
|
26
|
+
function toDescriptionHtml(raw) {
|
|
27
|
+
if (!raw)
|
|
28
|
+
return "";
|
|
29
|
+
const s = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
30
|
+
return normalizeRichTextHtml(s);
|
|
31
|
+
}
|
|
32
|
+
export async function LiveItemDetailPageView({ slug, initialProduct, renderActions }) {
|
|
33
|
+
const product = initialProduct !== undefined
|
|
34
|
+
? (initialProduct ?? undefined)
|
|
35
|
+
: await getLiveItemForDetail(slug).catch(() => undefined);
|
|
36
|
+
if (!product) {
|
|
37
|
+
return (_jsx(Main, { children: _jsx(Section, { padding: "y-5xl", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "start", gap: "md", className: "text-left", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: "Live Listing Not Found" }), _jsx(Text, { color: "muted", children: "The live listing you are looking for may have been removed." }), _jsx(Link, { href: String(ROUTES.PUBLIC.LIVE), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary-600 hover:underline", children: "Browse Live Listings" })] }) }) }) }));
|
|
38
|
+
}
|
|
39
|
+
const p = product;
|
|
40
|
+
const currency = p.currency || getDefaultCurrency();
|
|
41
|
+
const title = String(p.title ?? p.name ?? "Live Listing");
|
|
42
|
+
const price = typeof p.price === "number" ? p.price : null;
|
|
43
|
+
const images = Array.isArray(p.images) ? p.images : typeof p.mainImage === "string" ? [p.mainImage] : [];
|
|
44
|
+
const productVideo = p.video;
|
|
45
|
+
const meta = product.liveItem;
|
|
46
|
+
const jurisdictions = meta?.jurisdictionAllowed ?? [];
|
|
47
|
+
const transport = meta?.transport;
|
|
48
|
+
const category = Array.isArray(p.categorySlugs) && p.categorySlugs.length > 0 ? String(p.categorySlugs[0]) : (typeof p.category === "string" ? p.category : null);
|
|
49
|
+
const categoryName = Array.isArray(p.categoryNames) && p.categoryNames.length > 0 ? String(p.categoryNames[0]) : (typeof p.categoryName === "string" ? p.categoryName : null);
|
|
50
|
+
const brand = typeof p.brand === "string" ? p.brand : undefined;
|
|
51
|
+
const storeName = typeof p.storeName === "string" ? p.storeName : null;
|
|
52
|
+
const safeSeller = storeName ? safeDisplayName(storeName, "") : null;
|
|
53
|
+
const storeSlug = (typeof p.storeSlug === "string" ? p.storeSlug : null) || (typeof p.storeId === "string" ? p.storeId : null);
|
|
54
|
+
const storeHref = storeSlug ? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug)) : null;
|
|
55
|
+
const specs = Array.isArray(p.specifications) ? p.specifications : [];
|
|
56
|
+
const customSections = Array.isArray(p.customSections) ? p.customSections : [];
|
|
57
|
+
const descriptionHtml = toDescriptionHtml(p.description);
|
|
58
|
+
const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, reviews] = await Promise.all([
|
|
59
|
+
computeRelatedItems(product),
|
|
60
|
+
getGroupsWithItemsForProduct(product.id),
|
|
61
|
+
getReviewItemsForProduct(product.id),
|
|
62
|
+
]);
|
|
63
|
+
return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
|
|
64
|
+
title,
|
|
65
|
+
thumb: images[0],
|
|
66
|
+
price: price ?? undefined,
|
|
67
|
+
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
68
|
+
storeName: storeName ?? undefined,
|
|
69
|
+
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.LIVE), className: CLS_BREADCRUMB_LINK, children: "Live" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_ITEM_BADGE, children: "Live Item" }), meta?.vendorVerified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Verified Seller" })), meta?.cites && (_jsxs(Span, { color: "warning", surface: "warning-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: ["CITES: ", meta.cites] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title }), meta?.species && (_jsxs(Text, { className: "italic mt-1", color: "muted", size: "sm", children: [meta.species, meta.sex && meta.sex !== "n/a" && ` · ${meta.sex}`, meta.ageMonths !== undefined && ` · ${meta.ageMonths}mo`] }))] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), jurisdictions.length > 0 && (_jsxs(Div, { className: CLS_WARN_BOX, children: [_jsx(Text, { className: CLS_WARN_TITLE, children: "Delivery restrictions" }), _jsxs(Text, { className: CLS_WARN_BODY, children: ["This item can only be shipped to: ", jurisdictions.join(", ")] })] })), transport && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Transport" }), _jsxs(Text, { className: "text-muted-foreground", children: ["Method: ", transport.method, transport.handlingFee !== undefined && ` · Handling: ${formatCurrency(transport.handlingFee, "INR")}`, transport.insuranceIncluded && " · Insurance included"] })] })), meta?.careInfo && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Care information" }), _jsx(Text, { className: "mt-1 text-muted-foreground", children: meta.careInfo })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsList, { reviews: reviews, context: "listing", emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
|
|
70
|
+
id: s.id,
|
|
71
|
+
label: s.title,
|
|
72
|
+
content: _jsx(CustomSectionTabContent, { section: s }),
|
|
73
|
+
})) })), renderBuyBar: () => renderActions?.(product), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) })] })] }));
|
|
74
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import Image from "next/image";
|
|
4
|
-
import { useState } from "react";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
5
|
import { Div, Row, Span } from "../../ui";
|
|
6
6
|
import { ROUNDED_MAP, SHADOW_MAP } from "../../ui/components/surface-tokens";
|
|
7
7
|
import { resolveMediaUrl } from "../../utils/media-url";
|
|
@@ -25,6 +25,13 @@ const FALLBACK_ICONS = {
|
|
|
25
25
|
gallery: "📦",
|
|
26
26
|
avatar: "👤",
|
|
27
27
|
};
|
|
28
|
+
// A freshly-uploaded media slug can legitimately 404 for a brief window
|
|
29
|
+
// right after upload — the Firestore doc write and the Storage object write
|
|
30
|
+
// don't land atomically (see src/app/api/media/[...slug]/route.ts's own
|
|
31
|
+
// comment on this race). Retry with backoff before latching the fallback
|
|
32
|
+
// icon, instead of failing permanently on the very first request.
|
|
33
|
+
const MAX_LOAD_RETRIES = 3;
|
|
34
|
+
const RETRY_BACKOFF_MS = [600, 1200, 2400];
|
|
28
35
|
/** Resolve every URL in a comma-separated srcset string through the media proxy. */
|
|
29
36
|
function resolveSrcSet(input) {
|
|
30
37
|
return input
|
|
@@ -44,15 +51,39 @@ function resolveSrcSet(input) {
|
|
|
44
51
|
export function MediaImage({ src, alt, size = "card", priority = false, loading, objectFit = "cover", fallback, className, rounded, shadow, style, sources, onError, }) {
|
|
45
52
|
const [hasError, setHasError] = useState(false);
|
|
46
53
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
54
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
55
|
+
const retryTimeoutRef = useRef(null);
|
|
47
56
|
const icon = fallback ?? FALLBACK_ICONS[size];
|
|
48
57
|
const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
|
|
49
58
|
const resolvedSrc = resolveMediaUrl(src);
|
|
59
|
+
// Reset error/load/retry state whenever the resolved src actually changes.
|
|
60
|
+
// Without this, a component instance that hit a load failure once (e.g.
|
|
61
|
+
// lost the post-upload propagation race above) latches hasError forever,
|
|
62
|
+
// even across a later prop change to a src that would now succeed.
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
setHasError(false);
|
|
65
|
+
setIsLoaded(false);
|
|
66
|
+
setRetryCount(0);
|
|
67
|
+
return () => {
|
|
68
|
+
if (retryTimeoutRef.current) {
|
|
69
|
+
clearTimeout(retryTimeoutRef.current);
|
|
70
|
+
retryTimeoutRef.current = null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
74
|
+
}, [resolvedSrc]);
|
|
50
75
|
const wrapperExtra = [
|
|
51
76
|
rounded ? ROUNDED_MAP[rounded] : "",
|
|
52
77
|
shadow ? SHADOW_MAP[shadow] : "",
|
|
53
78
|
className ?? "",
|
|
54
79
|
].filter(Boolean).join(" ");
|
|
55
80
|
const handleError = () => {
|
|
81
|
+
if (retryCount < MAX_LOAD_RETRIES) {
|
|
82
|
+
retryTimeoutRef.current = setTimeout(() => {
|
|
83
|
+
setRetryCount((count) => count + 1);
|
|
84
|
+
}, RETRY_BACKOFF_MS[retryCount]);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
56
87
|
setHasError(true);
|
|
57
88
|
onError?.();
|
|
58
89
|
};
|
|
@@ -62,15 +93,20 @@ export function MediaImage({ src, alt, size = "card", priority = false, loading,
|
|
|
62
93
|
const isSvg = resolvedSrc.toLowerCase().endsWith(".svg") ||
|
|
63
94
|
resolvedSrc.includes("image/svg") ||
|
|
64
95
|
/[./]svg(\?|$)/i.test(resolvedSrc);
|
|
96
|
+
// Cache-bust the retried request so a browser-cached 404 response isn't
|
|
97
|
+
// silently re-served — the underlying URL is otherwise byte-identical.
|
|
98
|
+
const requestSrc = retryCount > 0
|
|
99
|
+
? `${resolvedSrc}${resolvedSrc.includes("?") ? "&" : "?"}retry=${retryCount}`
|
|
100
|
+
: resolvedSrc;
|
|
65
101
|
// Art-directed `<picture>` path — bypasses Next.js `<Image>` because the
|
|
66
102
|
// browser must pick the matching `<source>` itself. Loading + decoding hints
|
|
67
103
|
// come from the consumer's preset.
|
|
68
104
|
if (sources && sources.length > 0) {
|
|
69
|
-
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${wrapperExtra ? ` ${wrapperExtra}` : ""}`, style: style, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 animate-pulse", surface: "subtle", "aria-hidden": "true" })), _jsxs("picture", { children: [sources.map((source, index) => (_jsx("source", { srcSet: resolveSrcSet(source.srcSet), type: source.type, media: source.media }, `${source.media ?? ""}-${source.type ?? ""}-${index}`))), _jsx("img", { src:
|
|
105
|
+
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${wrapperExtra ? ` ${wrapperExtra}` : ""}`, style: style, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 animate-pulse", surface: "subtle", "aria-hidden": "true" })), _jsxs("picture", { children: [sources.map((source, index) => (_jsx("source", { srcSet: resolveSrcSet(source.srcSet), type: source.type, media: source.media }, `${source.media ?? ""}-${source.type ?? ""}-${index}`))), _jsx("img", { src: requestSrc, alt: alt, loading: loading ?? (priority ? "eager" : "lazy"), decoding: "async",
|
|
70
106
|
// catalogued primitive for media (variant catalogue allows the inner
|
|
71
107
|
// <img> here). Object-fit + className come from typed props.
|
|
72
108
|
className: `absolute inset-0 w-full h-full ${fitClass}`, onLoad: () => setIsLoaded(true), onError: handleError })] })] }));
|
|
73
109
|
}
|
|
74
|
-
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${wrapperExtra ? ` ${wrapperExtra}` : ""}`, style: style, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 animate-pulse", surface: "subtle", "aria-hidden": "true" })), _jsx(Image, { src:
|
|
110
|
+
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${wrapperExtra ? ` ${wrapperExtra}` : ""}`, style: style, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 animate-pulse", surface: "subtle", "aria-hidden": "true" })), _jsx(Image, { src: requestSrc, alt: alt, fill: true, priority: priority, loading: loading ?? (priority ? "eager" : "lazy"), className: fitClass, sizes: SIZE_HINTS[size], onLoad: () => setIsLoaded(true), onError: handleError, unoptimized: isSvg })] }));
|
|
75
111
|
}
|
|
76
112
|
export default MediaImage;
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useEffect } from "react";
|
|
4
4
|
import { Div, Row, Span } from "../../ui";
|
|
5
|
-
import { resolveMediaUrl } from "../../utils/media-url";
|
|
5
|
+
import { resolveMediaUrl, getYouTubeVideoId } from "../../utils/media-url";
|
|
6
6
|
import { useSiteSettings } from "../../core/hooks/useSiteSettings";
|
|
7
7
|
export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true, autoPlayMuted = false, loop = false, trimStart, trimEnd, objectFit = "cover", watermark, }) {
|
|
8
8
|
const videoRef = useRef(null);
|
|
9
9
|
const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
|
|
10
10
|
const resolvedSrc = resolveMediaUrl(src);
|
|
11
11
|
const resolvedPoster = resolveMediaUrl(thumbnailUrl);
|
|
12
|
+
const youtubeId = getYouTubeVideoId(src);
|
|
12
13
|
// Apply trimStart on load
|
|
13
14
|
useEffect(() => {
|
|
14
15
|
const el = videoRef.current;
|
|
@@ -46,6 +47,21 @@ export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true,
|
|
|
46
47
|
// audit-color-pair-function-ok: deliberate faded placeholder-icon shade, not a readability regression
|
|
47
48
|
className: "absolute inset-0 text-zinc-400 text-[length:var(--appkit-text-4xl)]", surface: "subtle", align: "center", justify: "center", role: "img", "aria-label": alt, children: _jsx(Span, { "aria-hidden": "true", children: "\uD83C\uDFAC" }) }));
|
|
48
49
|
}
|
|
50
|
+
// A YouTube watch/share URL (MediaUploadField's "YouTube" tab writes one)
|
|
51
|
+
// is never a raw playable file — <video src> fails with "no supported
|
|
52
|
+
// format" for it. Render the iframe embed instead of the native player.
|
|
53
|
+
if (youtubeId) {
|
|
54
|
+
const embedParams = new URLSearchParams({ playsinline: "1", rel: "0" });
|
|
55
|
+
if (autoPlayMuted) {
|
|
56
|
+
embedParams.set("autoplay", "1");
|
|
57
|
+
embedParams.set("mute", "1");
|
|
58
|
+
}
|
|
59
|
+
if (loop) {
|
|
60
|
+
embedParams.set("loop", "1");
|
|
61
|
+
embedParams.set("playlist", youtubeId);
|
|
62
|
+
}
|
|
63
|
+
return (_jsx("iframe", { src: `https://www.youtube-nocookie.com/embed/${youtubeId}?${embedParams.toString()}`, title: alt, allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true, className: "absolute inset-0 w-full h-full border-0" }));
|
|
64
|
+
}
|
|
49
65
|
return (_jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, src: resolvedSrc, poster: resolvedPoster, controls: controls, autoPlay: autoPlayMuted, muted: autoPlayMuted, loop: loop, playsInline: true, "aria-label": alt, className: `absolute inset-0 w-full h-full ${fitClass}` }), effectiveWatermark ? _jsx(MediaVideoWatermarkLayer, { config: effectiveWatermark }) : null] }));
|
|
50
66
|
}
|
|
51
67
|
/**
|
|
@@ -12,6 +12,13 @@ export interface MediaField {
|
|
|
12
12
|
source?: MediaSource;
|
|
13
13
|
/** YouTube video ID. Only set when source === "youtube". */
|
|
14
14
|
youtubeId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Duration in seconds, for `type: "video"`. Captured automatically for
|
|
17
|
+
* direct uploads (off-DOM `<video>` element, see `video-poster.ts`) — not
|
|
18
|
+
* obtainable client-side for `source: "youtube" | "external"` without a
|
|
19
|
+
* server round-trip, so it stays undefined for those sources.
|
|
20
|
+
*/
|
|
21
|
+
duration?: number;
|
|
15
22
|
}
|
|
16
23
|
export declare const mediaFieldSchema: z.ZodObject<{
|
|
17
24
|
url: z.ZodString;
|
|
@@ -20,6 +27,7 @@ export declare const mediaFieldSchema: z.ZodObject<{
|
|
|
20
27
|
thumbnailUrl: z.ZodOptional<z.ZodString>;
|
|
21
28
|
source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
|
|
22
29
|
youtubeId: z.ZodOptional<z.ZodString>;
|
|
30
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
23
31
|
}, "strip", z.ZodTypeAny, {
|
|
24
32
|
type: "video" | "image" | "file";
|
|
25
33
|
url: string;
|
|
@@ -27,6 +35,7 @@ export declare const mediaFieldSchema: z.ZodObject<{
|
|
|
27
35
|
alt?: string | undefined;
|
|
28
36
|
thumbnailUrl?: string | undefined;
|
|
29
37
|
youtubeId?: string | undefined;
|
|
38
|
+
duration?: number | undefined;
|
|
30
39
|
}, {
|
|
31
40
|
type: "video" | "image" | "file";
|
|
32
41
|
url: string;
|
|
@@ -34,6 +43,7 @@ export declare const mediaFieldSchema: z.ZodObject<{
|
|
|
34
43
|
alt?: string | undefined;
|
|
35
44
|
thumbnailUrl?: string | undefined;
|
|
36
45
|
youtubeId?: string | undefined;
|
|
46
|
+
duration?: number | undefined;
|
|
37
47
|
}>;
|
|
38
48
|
export declare function coerceMediaField(value: MediaFieldInput, fallbackType?: MediaFieldType): MediaField | null;
|
|
39
49
|
export declare function coerceMediaFieldArray(values: Array<MediaField | string> | null | undefined, fallbackType?: MediaFieldType): MediaField[];
|
|
@@ -7,6 +7,7 @@ export const mediaFieldSchema = z.object({
|
|
|
7
7
|
thumbnailUrl: z.string().optional(),
|
|
8
8
|
source: z.enum(["upload", "youtube", "external"]).optional(),
|
|
9
9
|
youtubeId: z.string().optional(),
|
|
10
|
+
duration: z.number().optional(),
|
|
10
11
|
});
|
|
11
12
|
export function coerceMediaField(value, fallbackType = "image") {
|
|
12
13
|
if (!value) {
|
|
@@ -28,6 +28,12 @@ export interface MediaUploadFieldProps {
|
|
|
28
28
|
enableTrim?: boolean;
|
|
29
29
|
enableThumbnail?: boolean;
|
|
30
30
|
onThumbnailChange?: (url: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Fired with the captured duration (seconds) for a direct video upload —
|
|
33
|
+
* mirrors `onThumbnailChange`. Never fires for the YouTube/External-URL
|
|
34
|
+
* tabs (no client-side way to read those without a server round-trip).
|
|
35
|
+
*/
|
|
36
|
+
onDurationChange?: (duration: number) => void;
|
|
31
37
|
/** Show a "YouTube URL" tab to embed a YouTube video by ID or URL. */
|
|
32
38
|
showYoutube?: boolean;
|
|
33
39
|
/** Show an "External URL" tab to link a third-party image/video URL. */
|
|
@@ -63,4 +69,4 @@ export interface MediaUploadFieldProps {
|
|
|
63
69
|
*/
|
|
64
70
|
autoCapturePoster?: boolean;
|
|
65
71
|
}
|
|
66
|
-
export declare function MediaUploadField({ label, value, onChange, onChangeField, onUpload, kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled, helperText, captureSource, captureMode, enableTrim, enableThumbnail, onThumbnailChange, showYoutube, showExternal, onAbort, onStagedUrlsChange, isPersisted, multiple, onAddUrl, autoCapturePoster, }: MediaUploadFieldProps): import("react").JSX.Element;
|
|
72
|
+
export declare function MediaUploadField({ label, value, onChange, onChangeField, onUpload, kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled, helperText, captureSource, captureMode, enableTrim, enableThumbnail, onThumbnailChange, onDurationChange, showYoutube, showExternal, onAbort, onStagedUrlsChange, isPersisted, multiple, onAddUrl, autoCapturePoster, }: MediaUploadFieldProps): import("react").JSX.Element;
|
|
@@ -88,7 +88,7 @@ export function MediaUploadField({ label, value, onChange, onChangeField, onUplo
|
|
|
88
88
|
// SB-UNI-Z5 2026-05-13 — `kind` derives accept/maxSizeMB when not set
|
|
89
89
|
// explicitly; explicit props still win. "*" + 50 stay as the floor for
|
|
90
90
|
// back-compat with callers that pass neither.
|
|
91
|
-
kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube = true, showExternal = true, onAbort, onStagedUrlsChange, isPersisted = false, multiple = false, onAddUrl, autoCapturePoster = true, }) {
|
|
91
|
+
kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, onDurationChange, showYoutube = true, showExternal = true, onAbort, onStagedUrlsChange, isPersisted = false, multiple = false, onAddUrl, autoCapturePoster = true, }) {
|
|
92
92
|
const [isLoading, setIsLoading] = useState(false);
|
|
93
93
|
const [error, setError] = useState(null);
|
|
94
94
|
const [pendingVideoUrl, setPendingVideoUrl] = useState(null);
|
|
@@ -198,14 +198,18 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
198
198
|
: captureMode === "both"
|
|
199
199
|
? "image/*,video/*"
|
|
200
200
|
: "image/*";
|
|
201
|
-
const afterUpload = (url, fileType, autoPosterUrl) => {
|
|
201
|
+
const afterUpload = (url, fileType, autoPosterUrl, duration) => {
|
|
202
202
|
onChangeField?.({
|
|
203
203
|
url,
|
|
204
204
|
type: inferMediaTypeFromMime(fileType, url),
|
|
205
|
+
source: "upload",
|
|
205
206
|
...(autoPosterUrl ? { thumbnailUrl: autoPosterUrl } : {}),
|
|
207
|
+
...(duration ? { duration } : {}),
|
|
206
208
|
});
|
|
207
209
|
if (autoPosterUrl)
|
|
208
210
|
onThumbnailChange?.(autoPosterUrl);
|
|
211
|
+
if (duration)
|
|
212
|
+
onDurationChange?.(duration);
|
|
209
213
|
const isVideoFile = fileType.startsWith("video/");
|
|
210
214
|
if (isVideoFile && enableTrim) {
|
|
211
215
|
setPendingVideoUrl(url);
|
|
@@ -283,7 +287,7 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
283
287
|
const posterFile = posterBlobAsFile(poster, file.name);
|
|
284
288
|
const posterUrl = await onUpload(posterFile);
|
|
285
289
|
stageUrl(posterUrl);
|
|
286
|
-
return posterUrl;
|
|
290
|
+
return { posterUrl, duration: poster.duration };
|
|
287
291
|
}
|
|
288
292
|
catch (err) {
|
|
289
293
|
// The upload-poster step is best-effort. Log via normalizeError so any
|
|
@@ -309,8 +313,8 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
309
313
|
else {
|
|
310
314
|
const result = await uploadSingleFile(files[0]);
|
|
311
315
|
if (result) {
|
|
312
|
-
const
|
|
313
|
-
afterUpload(result.url, result.type, posterUrl);
|
|
316
|
+
const poster = await maybeCaptureVideoPoster(files[0]);
|
|
317
|
+
afterUpload(result.url, result.type, poster?.posterUrl, poster?.duration);
|
|
314
318
|
}
|
|
315
319
|
}
|
|
316
320
|
}
|
|
@@ -353,8 +357,8 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
353
357
|
try {
|
|
354
358
|
const url = await onUpload(file);
|
|
355
359
|
stageUrl(url);
|
|
356
|
-
const
|
|
357
|
-
afterUpload(url, blob.type || mimeType, posterUrl);
|
|
360
|
+
const poster = await maybeCaptureVideoPoster(file);
|
|
361
|
+
afterUpload(url, blob.type || mimeType, poster?.posterUrl, poster?.duration);
|
|
358
362
|
}
|
|
359
363
|
catch (err) {
|
|
360
364
|
void normalizeError(err);
|
|
@@ -26,6 +26,8 @@ export interface ExtractedPoster {
|
|
|
26
26
|
/** Frame dimensions in pixels (also the JPEG's pixel dimensions). */
|
|
27
27
|
width: number;
|
|
28
28
|
height: number;
|
|
29
|
+
/** Video duration in seconds, read from the same off-DOM element. */
|
|
30
|
+
duration: number;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Build a deterministic filename for the captured poster. We mirror the
|
|
@@ -25,6 +25,20 @@ const TARGET_FRAME_TIME_SECONDS = 0.5;
|
|
|
25
25
|
const JPEG_QUALITY = 0.85;
|
|
26
26
|
const LOAD_TIMEOUT_MS = 10000;
|
|
27
27
|
const POSTER_MIME = "image/jpeg";
|
|
28
|
+
/** Bounded wait for `video.duration` to become a finite, positive number. */
|
|
29
|
+
async function readDuration(video) {
|
|
30
|
+
if (Number.isFinite(video.duration) && video.duration > 0)
|
|
31
|
+
return video.duration;
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const timer = window.setTimeout(() => resolve(0), 2000);
|
|
34
|
+
video.addEventListener("durationchange", () => {
|
|
35
|
+
if (Number.isFinite(video.duration) && video.duration > 0) {
|
|
36
|
+
window.clearTimeout(timer);
|
|
37
|
+
resolve(video.duration);
|
|
38
|
+
}
|
|
39
|
+
}, { once: true });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
28
42
|
/**
|
|
29
43
|
* Build a deterministic filename for the captured poster. We mirror the
|
|
30
44
|
* source video's basename so downstream code can correlate the two assets
|
|
@@ -86,6 +100,7 @@ export async function extractVideoPosterFrame(file) {
|
|
|
86
100
|
const height = video.videoHeight;
|
|
87
101
|
if (!width || !height)
|
|
88
102
|
return null;
|
|
103
|
+
const duration = await readDuration(video);
|
|
89
104
|
const canvas = document.createElement("canvas");
|
|
90
105
|
canvas.width = width;
|
|
91
106
|
canvas.height = height;
|
|
@@ -96,7 +111,7 @@ export async function extractVideoPosterFrame(file) {
|
|
|
96
111
|
const blob = await new Promise((resolve) => canvas.toBlob(resolve, POSTER_MIME, JPEG_QUALITY));
|
|
97
112
|
if (!blob)
|
|
98
113
|
return null;
|
|
99
|
-
return { blob, width, height };
|
|
114
|
+
return { blob, width, height, duration };
|
|
100
115
|
}
|
|
101
116
|
catch (_err) {
|
|
102
117
|
void normalizeError(_err);
|
|
@@ -19,7 +19,7 @@ describe("posterBlobAsFile", () => {
|
|
|
19
19
|
const blob = new Blob([new Uint8Array([0xff, 0xd8, 0xff])], {
|
|
20
20
|
type: "image/jpeg",
|
|
21
21
|
});
|
|
22
|
-
const file = posterBlobAsFile({ blob, width: 320, height: 180 }, "promo-reel.mp4");
|
|
22
|
+
const file = posterBlobAsFile({ blob, width: 320, height: 180, duration: 12.5 }, "promo-reel.mp4");
|
|
23
23
|
expect(file).toBeInstanceOf(File);
|
|
24
24
|
expect(file.name).toBe("promo-reel.poster.jpg");
|
|
25
25
|
expect(file.type).toBe("image/jpeg");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface OrderStatusTimelineProps {
|
|
2
|
+
orderStatus: string;
|
|
3
|
+
orderDate?: string;
|
|
4
|
+
shippingDate?: string;
|
|
5
|
+
deliveryDate?: string;
|
|
6
|
+
cancellationDate?: string;
|
|
7
|
+
trackingNumber?: string;
|
|
8
|
+
shippingCarrier?: string;
|
|
9
|
+
trackingUrl?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function OrderStatusTimeline({ orderStatus, orderDate, shippingDate, deliveryDate, cancellationDate, trackingNumber, shippingCarrier, trackingUrl, className, }: OrderStatusTimelineProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* OrderStatusTimeline — a genuine (not fabricated/estimated) status timeline
|
|
5
|
+
* built from the real per-status timestamps already written by the
|
|
6
|
+
* order-status-update path (`orderDate`/`shippingDate`/`deliveryDate`/
|
|
7
|
+
* `cancellationDate`). No live courier-API integration — `trackingUrl` is
|
|
8
|
+
* just the passthrough link a seller/admin entered, rendered as an external
|
|
9
|
+
* link that opens in a new tab. Statuses with no dedicated date field
|
|
10
|
+
* (confirmed/processing/return_requested/returned/refunded) render as a step
|
|
11
|
+
* without a timestamp rather than showing a made-up date.
|
|
12
|
+
*/
|
|
13
|
+
import { Anchor, Badge, Div, Heading, Row, Stack, Text } from "../../../ui";
|
|
14
|
+
const STAGE_SEQUENCE = ["pending", "confirmed", "processing", "shipped", "delivered"];
|
|
15
|
+
const STAGE_LABELS = {
|
|
16
|
+
pending: "Order placed",
|
|
17
|
+
confirmed: "Confirmed",
|
|
18
|
+
processing: "Processing",
|
|
19
|
+
shipped: "Shipped",
|
|
20
|
+
delivered: "Delivered",
|
|
21
|
+
cancelled: "Cancelled",
|
|
22
|
+
return_requested: "Return requested",
|
|
23
|
+
returned: "Returned",
|
|
24
|
+
refunded: "Refunded",
|
|
25
|
+
};
|
|
26
|
+
function buildSteps(props) {
|
|
27
|
+
const status = (props.orderStatus ?? "").toLowerCase();
|
|
28
|
+
const currentIndex = STAGE_SEQUENCE.indexOf(status);
|
|
29
|
+
const steps = [{ key: "pending", label: STAGE_LABELS.pending, date: props.orderDate }];
|
|
30
|
+
if ((status === "confirmed" || status === "processing") && !props.shippingDate) {
|
|
31
|
+
steps.push({ key: status, label: STAGE_LABELS[status] });
|
|
32
|
+
}
|
|
33
|
+
if (props.shippingDate || currentIndex >= 3) {
|
|
34
|
+
steps.push({ key: "shipped", label: STAGE_LABELS.shipped, date: props.shippingDate });
|
|
35
|
+
}
|
|
36
|
+
if (props.deliveryDate || status === "delivered") {
|
|
37
|
+
steps.push({ key: "delivered", label: STAGE_LABELS.delivered, date: props.deliveryDate });
|
|
38
|
+
}
|
|
39
|
+
if (status === "cancelled" || props.cancellationDate) {
|
|
40
|
+
steps.push({ key: "cancelled", label: STAGE_LABELS.cancelled, date: props.cancellationDate });
|
|
41
|
+
}
|
|
42
|
+
else if (status === "return_requested" || status === "returned" || status === "refunded") {
|
|
43
|
+
steps.push({ key: status, label: STAGE_LABELS[status] ?? status });
|
|
44
|
+
}
|
|
45
|
+
return steps;
|
|
46
|
+
}
|
|
47
|
+
export function OrderStatusTimeline({ orderStatus, orderDate, shippingDate, deliveryDate, cancellationDate, trackingNumber, shippingCarrier, trackingUrl, className = "", }) {
|
|
48
|
+
const steps = buildSteps({ orderStatus, orderDate, shippingDate, deliveryDate, cancellationDate });
|
|
49
|
+
const isTerminalNegative = steps[steps.length - 1]?.key === "cancelled";
|
|
50
|
+
return (_jsxs(Div, { className: className, rounded: "xl", padding: "md", surface: "muted", border: "default", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "muted", className: "mb-3", children: "Tracking" }), _jsx(Stack, { gap: "none", children: steps.map((step, i) => {
|
|
51
|
+
const isLast = i === steps.length - 1;
|
|
52
|
+
const isNegative = step.key === "cancelled";
|
|
53
|
+
return (_jsxs(Row, { gap: "sm", padding: isLast ? "none" : "b-md", children: [_jsxs(Stack, { gap: "none", align: "center", className: "shrink-0", children: [_jsx(Div, { className: `h-2.5 w-2.5 ${isNegative ? "" : "bg-[var(--appkit-color-primary)]"}`, rounded: "full", surface: isNegative ? "danger-surface" : "none" }), !isLast && _jsx(Div, { className: "w-px flex-1 min-h-[24px]", surface: "subtle" })] }), _jsxs(Stack, { gap: "none", className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", weight: "medium", className: isNegative ? "text-error" : undefined, children: step.label }), _jsx(Text, { size: "xs", color: "muted", children: step.date ? new Date(step.date).toLocaleString() : "—" })] })] }, step.key));
|
|
54
|
+
}) }), (trackingNumber || shippingCarrier || trackingUrl) && !isTerminalNegative && (_jsx(Div, { className: "mt-3 border-t border-[var(--appkit-color-border)]", padding: "t-sm", children: _jsxs(Stack, { gap: "xs", children: [shippingCarrier && (_jsxs(Row, { justify: "between", children: [_jsx(Text, { size: "sm", color: "muted", children: "Carrier" }), _jsx(Text, { size: "sm", weight: "medium", children: shippingCarrier })] })), trackingNumber && (_jsxs(Row, { justify: "between", children: [_jsx(Text, { size: "sm", color: "muted", children: "Tracking number" }), _jsx(Text, { size: "sm", weight: "medium", children: trackingNumber })] })), trackingUrl && (_jsx(Row, { justify: "end", children: _jsx(Anchor, { href: trackingUrl, tone: "brand", weight: "semibold", size: "sm", children: "Track with carrier \u2192" }) }))] }) })), !trackingNumber && !trackingUrl && !isTerminalNegative && (_jsx(Badge, { variant: "default", className: "mt-3", children: "No tracking details yet" }))] }));
|
|
55
|
+
}
|
|
@@ -6,6 +6,8 @@ export { OrderSiblingPayments } from "./OrderSiblingPayments";
|
|
|
6
6
|
export type { OrderSiblingPaymentsProps } from "./OrderSiblingPayments";
|
|
7
7
|
export { OrderPaymentSummary } from "./OrderPaymentSummary";
|
|
8
8
|
export type { OrderPaymentSummaryProps } from "./OrderPaymentSummary";
|
|
9
|
+
export { OrderStatusTimeline } from "./OrderStatusTimeline";
|
|
10
|
+
export type { OrderStatusTimelineProps } from "./OrderStatusTimeline";
|
|
9
11
|
export { RefundHistoryTable } from "./RefundHistoryTable";
|
|
10
12
|
export type { RefundHistoryTableProps } from "./RefundHistoryTable";
|
|
11
13
|
export { RefundRequestView } from "./RefundRequestView";
|
|
@@ -3,5 +3,6 @@ export { MarketplaceOrderCard, } from "./MarketplaceOrderCard";
|
|
|
3
3
|
export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters";
|
|
4
4
|
export { OrderSiblingPayments } from "./OrderSiblingPayments";
|
|
5
5
|
export { OrderPaymentSummary } from "./OrderPaymentSummary";
|
|
6
|
+
export { OrderStatusTimeline } from "./OrderStatusTimeline";
|
|
6
7
|
export { RefundHistoryTable } from "./RefundHistoryTable";
|
|
7
8
|
export { RefundRequestView } from "./RefundRequestView";
|