@mohasinac/appkit 4.59.0 → 4.60.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/features/layout/DashboardLayoutClient.js +62 -5
- package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
- package/dist/_internal/server/features/brands/actions.js +78 -4
- package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
- package/dist/_internal/server/features/checkout/actions.js +668 -183
- package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
- package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
- package/dist/_internal/server/features/gateway-events/index.js +10 -0
- package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
- package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
- package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
- package/dist/_internal/server/features/gateway-events/redact.js +151 -0
- package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
- package/dist/_internal/server/features/grievance/actions.js +173 -0
- package/dist/_internal/server/features/grievance/index.d.ts +8 -0
- package/dist/_internal/server/features/grievance/index.js +8 -0
- package/dist/_internal/server/features/grouped/data.js +28 -3
- package/dist/_internal/server/features/lottery/actions.js +99 -4
- package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
- package/dist/_internal/server/features/order-completion/actions.js +336 -0
- package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
- package/dist/_internal/server/features/order-completion/index.js +7 -0
- package/dist/_internal/server/features/orders/actions.d.ts +13 -0
- package/dist/_internal/server/features/orders/actions.js +56 -2
- package/dist/_internal/server/features/orders/adapters.js +26 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
- package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
- package/dist/_internal/server/features/orders/index.d.ts +2 -1
- package/dist/_internal/server/features/orders/index.js +1 -1
- package/dist/_internal/server/features/orders/service.d.ts +60 -0
- package/dist/_internal/server/features/orders/service.js +74 -0
- package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
- package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
- package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
- package/dist/_internal/server/features/payment-cases/index.js +9 -0
- package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
- package/dist/_internal/server/features/payouts/actions.js +84 -10
- package/dist/_internal/server/features/points/adjust.d.ts +61 -0
- package/dist/_internal/server/features/points/adjust.js +121 -0
- package/dist/_internal/server/features/points/award.d.ts +65 -0
- package/dist/_internal/server/features/points/award.js +145 -0
- package/dist/_internal/server/features/price-index/market.d.ts +33 -0
- package/dist/_internal/server/features/price-index/market.js +54 -1
- package/dist/_internal/server/features/products/data.js +40 -22
- package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
- package/dist/_internal/server/features/protection-claims/actions.js +228 -0
- package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
- package/dist/_internal/server/features/protection-claims/index.js +8 -0
- package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
- package/dist/_internal/server/features/refunds/actions.js +259 -17
- package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
- package/dist/_internal/server/features/refunds/capacity.js +37 -0
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/search/index-build/sources.js +20 -1
- package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
- package/dist/_internal/server/features/shipping/reconcile.js +34 -3
- package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
- package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
- package/dist/_internal/server/features/stores/tabs.js +24 -4
- package/dist/_internal/server/functions/https.d.ts +31 -1
- package/dist/_internal/server/functions/https.js +52 -1
- package/dist/_internal/server/functions/scheduled.d.ts +72 -1
- package/dist/_internal/server/functions/scheduled.js +124 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
- package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
- package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
- package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
- package/dist/_internal/server/jobs/core/index.d.ts +2 -0
- package/dist/_internal/server/jobs/core/index.js +2 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
- package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
- package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
- package/dist/_internal/server/jobs/handlers/index.js +5 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
- package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +71 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
- package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
- package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
- package/dist/_internal/shared/checkout/lanes.js +46 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
- package/dist/_internal/shared/checkout/order-math.js +251 -3
- package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
- package/dist/_internal/shared/checkout/refundable-base.js +122 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
- package/dist/_internal/shared/features/auctions/config.js +30 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
- package/dist/_internal/shared/features/brands/schema.js +18 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
- package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
- package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
- package/dist/_internal/shared/features/print/label-profiles.js +350 -0
- package/dist/_internal/shared/features/products/to-product-item.js +33 -5
- package/dist/_internal/shared/fees/calculator.d.ts +31 -2
- package/dist/_internal/shared/fees/calculator.js +103 -5
- package/dist/_internal/shared/fees/india-states.d.ts +195 -0
- package/dist/_internal/shared/fees/india-states.js +190 -0
- package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
- package/dist/_internal/shared/fees/payout-hold.js +74 -0
- package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
- package/dist/_internal/shared/obligations/deadlines.js +155 -0
- package/dist/_internal/shared/shipping/quote.d.ts +96 -0
- package/dist/_internal/shared/shipping/quote.js +67 -0
- package/dist/_internal/shared/time/business-days.d.ts +91 -0
- package/dist/_internal/shared/time/business-days.js +185 -0
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +9 -4
- package/dist/client.js +26 -3
- package/dist/configs/tailwind.d.ts +0 -12
- package/dist/configs/tailwind.js +28 -15
- package/dist/constants/api-endpoints.d.ts +51 -9
- package/dist/constants/api-endpoints.js +23 -7
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +131 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/about/components/DeveloperView.js +5 -2
- package/dist/features/about/components/FAQPageView.js +2 -1
- package/dist/features/about/components/FeesView.js +2 -1
- package/dist/features/about/components/HelpPageView.js +2 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
- package/dist/features/about/components/HowEmiWorksView.js +2 -1
- package/dist/features/about/components/HowOffersWorkView.js +2 -1
- package/dist/features/about/components/HowOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowReviewsWorkView.js +2 -1
- package/dist/features/about/components/PolicyPageView.js +2 -1
- package/dist/features/about/components/ProseHero.d.ts +73 -0
- package/dist/features/about/components/ProseHero.js +18 -0
- package/dist/features/about/components/SecurityPrivacyView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +2 -1
- package/dist/features/about/components/TrackOrderView.js +2 -1
- package/dist/features/about/components/UnauthorizedView.js +1 -1
- package/dist/features/about/index.d.ts +2 -0
- package/dist/features/about/index.js +5 -0
- package/dist/features/account/actions/address-actions.d.ts +26 -2
- package/dist/features/account/actions/address-actions.js +25 -1
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOrdersView.js +17 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/hooks/useNotifications.js +29 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
- package/dist/features/admin/components/AdminBeysView.js +2 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCategoriesView.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
- package/dist/features/admin/components/AdminContactEditorView.js +1 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
- package/dist/features/admin/components/AdminFaqsView.js +2 -2
- package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
- package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
- package/dist/features/admin/components/AdminPartsView.js +2 -2
- package/dist/features/admin/components/AdminProductsView.js +75 -4
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
- package/dist/features/admin/components/AdminUsersView.js +72 -1
- package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -4
- package/dist/features/admin/components/DataListingView.d.ts +28 -0
- package/dist/features/admin/components/DataListingView.js +175 -14
- package/dist/features/admin/components/DataTable.d.ts +12 -1
- package/dist/features/admin/components/DataTable.js +3 -3
- package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/OwnerQuickActions.js +60 -2
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
- package/dist/features/admin/components/index.d.ts +4 -0
- package/dist/features/admin/components/index.js +6 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
- package/dist/features/admin/constants/filter-tabs.js +106 -0
- package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
- package/dist/features/admin/repository/site-settings.repository.js +82 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
- package/dist/features/admin/schemas/firestore.js +5 -1
- package/dist/features/admin/schemas/site-settings-form.js +30 -1
- package/dist/features/auctions/actions/bid-actions.js +13 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
- package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
- package/dist/features/audit-log/schemas/firestore.js +14 -0
- package/dist/features/auth/permissions/constants.d.ts +17 -1
- package/dist/features/auth/permissions/constants.js +18 -0
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
- package/dist/features/categories/api/[id]/description-template/route.js +107 -1
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
- package/dist/features/categories/components/BundleDetailView.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryForm.js +12 -1
- package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
- package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
- package/dist/features/categories/repository/market-price-index.repository.js +105 -0
- package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
- package/dist/features/categories/repository/price-observation.repository.js +132 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -1
- package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
- package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
- package/dist/features/faq/components/RelatedFAQs.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
- package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
- package/dist/features/gateway-events/schemas/firestore.js +125 -0
- package/dist/features/gateway-events/schemas/index.d.ts +81 -0
- package/dist/features/gateway-events/schemas/index.js +79 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.js +37 -1
- package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
- package/dist/features/homepage/components/HeroCarousel.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
- package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
- package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
- package/dist/features/inventory/components/ReceiveStationView.js +413 -0
- package/dist/features/inventory/components/operator-message.d.ts +29 -0
- package/dist/features/inventory/components/operator-message.js +41 -0
- package/dist/features/inventory/index.d.ts +13 -0
- package/dist/features/inventory/index.js +28 -0
- package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
- package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
- package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
- package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
- package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
- package/dist/features/inventory/schemas/firestore.d.ts +197 -0
- package/dist/features/inventory/schemas/firestore.js +159 -0
- package/dist/features/layout/AppLayoutShell.d.ts +12 -1
- package/dist/features/layout/AppLayoutShell.js +25 -12
- package/dist/features/layout/AutoBreadcrumbs.js +162 -3
- package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
- package/dist/features/layout/BottomNavbar.d.ts +1 -1
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/media/MediaAvatar.js +1 -1
- package/dist/features/media/MediaImage.d.ts +24 -4
- package/dist/features/media/MediaImage.js +44 -4
- package/dist/features/media/MediaLightbox.js +1 -1
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +91 -3
- package/dist/features/obligations/constants/policies.d.ts +92 -0
- package/dist/features/obligations/constants/policies.js +378 -0
- package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
- package/dist/features/obligations/repository/obligations.repository.js +158 -0
- package/dist/features/obligations/schemas/firestore.d.ts +160 -0
- package/dist/features/obligations/schemas/firestore.js +87 -0
- package/dist/features/orders/actions/order-actions.d.ts +43 -4
- package/dist/features/orders/actions/order-actions.js +60 -3
- package/dist/features/orders/actions/refund-actions.js +70 -10
- package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
- package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +3 -1
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +6 -0
- package/dist/features/orders/constants/transitions.d.ts +59 -0
- package/dist/features/orders/constants/transitions.js +104 -0
- package/dist/features/orders/repository/orders.repository.d.ts +109 -6
- package/dist/features/orders/repository/orders.repository.js +464 -21
- package/dist/features/orders/schemas/firestore.d.ts +270 -0
- package/dist/features/orders/schemas/firestore.js +24 -0
- package/dist/features/orders/schemas/index.d.ts +61 -0
- package/dist/features/orders/schemas/index.js +22 -0
- package/dist/features/orders/types/index.d.ts +79 -0
- package/dist/features/parts/components/PartsIndexPageView.js +8 -1
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
- package/dist/features/payment-cases/schemas/firestore.js +105 -0
- package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
- package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
- package/dist/features/payments/repository/payout.repository.d.ts +0 -1
- package/dist/features/payments/repository/payout.repository.js +0 -1
- package/dist/features/points/repository/points.repository.d.ts +122 -0
- package/dist/features/points/repository/points.repository.js +304 -1
- package/dist/features/points/schemas/adjust-form.d.ts +58 -0
- package/dist/features/points/schemas/adjust-form.js +63 -0
- package/dist/features/points/schemas/firestore.d.ts +108 -1
- package/dist/features/points/schemas/firestore.js +84 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
- package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
- package/dist/features/price-index/market-stats.d.ts +82 -0
- package/dist/features/price-index/market-stats.js +230 -0
- package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
- package/dist/features/print/components/BoxLabelPrintView.js +66 -0
- package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
- package/dist/features/print/components/LabelProfilePicker.js +38 -0
- package/dist/features/print/components/labels.d.ts +117 -0
- package/dist/features/print/components/labels.js +258 -0
- package/dist/features/print/components/labels.style.css +46 -0
- package/dist/features/print/index.d.ts +6 -0
- package/dist/features/print/index.js +16 -0
- package/dist/features/products/columns/productTableColumns.js +5 -4
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +34 -23
- package/dist/features/products/components/ProductGrid.js +86 -62
- package/dist/features/products/constants/card-badges.d.ts +97 -0
- package/dist/features/products/constants/card-badges.js +100 -0
- package/dist/features/products/repository/products.repository.js +34 -1
- package/dist/features/products/schemas/firestore.d.ts +46 -3
- package/dist/features/products/schemas/firestore.js +21 -0
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
- package/dist/features/promotions/schemas/firestore.js +30 -3
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
- package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
- package/dist/features/protection-claims/schemas/firestore.js +130 -0
- package/dist/features/reviews/actions/review-actions.js +3 -2
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/schemas/firestore.d.ts +20 -1
- package/dist/features/scams/constants/scam-types.d.ts +2 -0
- package/dist/features/scams/constants/scam-types.js +18 -0
- package/dist/features/scams/schemas/report-form.d.ts +3 -3
- package/dist/features/scams/schemas/report-form.js +26 -1
- package/dist/features/search/components/Search.js +9 -9
- package/dist/features/search/components/SearchResultsClient.js +18 -3
- package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/seller/actions/offer-actions.js +16 -1
- package/dist/features/seller/actions/seller-actions.js +14 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
- package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
- package/dist/features/seller/components/PrintCenterView.js +41 -30
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
- package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
- package/dist/features/seller/components/SellerProductShell.js +123 -13
- package/dist/features/seller/components/SellerReviewsView.js +4 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
- package/dist/features/seller/schemas/coupon-form.js +27 -0
- package/dist/features/seller/schemas/product-form.d.ts +3 -0
- package/dist/features/seller/schemas/product-form.js +5 -0
- package/dist/features/shell/FormShell.js +2 -2
- package/dist/features/shell/QuickFormDrawer.js +14 -0
- package/dist/features/shell/SectionForm.d.ts +19 -0
- package/dist/features/shell/SectionForm.js +133 -9
- package/dist/features/shell/SectionForm.style.css +54 -0
- package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
- package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
- package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/support/schemas/firestore.d.ts +73 -2
- package/dist/features/support/schemas/firestore.js +59 -0
- package/dist/index.d.ts +16 -24
- package/dist/index.js +38 -47
- package/dist/next/ErrorBoundary.js +1 -1
- package/dist/next/routing/route-map.d.ts +24 -0
- package/dist/next/routing/route-map.js +40 -0
- package/dist/providers/payment-manual/index.d.ts +15 -1
- package/dist/providers/payment-manual/index.js +37 -0
- package/dist/providers/payment-phonepe/index.d.ts +14 -1
- package/dist/providers/payment-phonepe/index.js +36 -0
- package/dist/providers/payment-razorpay/index.d.ts +44 -1
- package/dist/providers/payment-razorpay/index.js +99 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
- package/dist/providers/shipping-shiprocket/index.js +18 -0
- package/dist/schemas/registry.d.ts +44 -11
- package/dist/server-entry.d.ts +2 -0
- package/dist/server-entry.js +27 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +93 -0
- package/dist/styles.css +418 -149
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +15 -12
- package/dist/tokens/themes/default-light.js +14 -11
- package/dist/tokens/tokens.css +106 -23
- package/dist/ui/DataTable.style.css +4 -4
- package/dist/ui/columns/cell-renderers.d.ts +20 -0
- package/dist/ui/columns/cell-renderers.js +26 -1
- package/dist/ui/columns/index.d.ts +1 -1
- package/dist/ui/columns/index.js +4 -1
- package/dist/ui/components/Badge.style.css +1 -1
- package/dist/ui/components/Breadcrumb.js +19 -1
- package/dist/ui/components/BulkActionBar.style.css +4 -4
- package/dist/ui/components/Button.style.css +1 -1
- package/dist/ui/components/Checkbox.style.css +1 -1
- package/dist/ui/components/FallbackShell.js +1 -1
- package/dist/ui/components/Form.d.ts +14 -1
- package/dist/ui/components/Form.js +2 -2
- package/dist/ui/components/FormField.js +18 -4
- package/dist/ui/components/FormField.style.css +43 -3
- package/dist/ui/components/GatedPrice.style.css +1 -1
- package/dist/ui/components/IconButton.style.css +1 -1
- package/dist/ui/components/Input.js +22 -1
- package/dist/ui/components/Input.style.css +38 -22
- package/dist/ui/components/ListingFilterDrawer.js +1 -1
- package/dist/ui/components/ListingToolbar.js +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/Radio.style.css +1 -1
- package/dist/ui/components/Repeater.d.ts +53 -0
- package/dist/ui/components/Repeater.js +7 -0
- package/dist/ui/components/RichTextEditor.d.ts +20 -1
- package/dist/ui/components/RichTextEditor.js +23 -2
- package/dist/ui/components/Select.style.css +7 -21
- package/dist/ui/components/Semantic.js +27 -3
- package/dist/ui/components/TagInput.style.css +4 -9
- package/dist/ui/components/Textarea.style.css +4 -18
- package/dist/ui/components/Toggle.style.css +1 -1
- package/dist/ui/components/Typography.d.ts +3 -3
- package/dist/ui/components/Typography.js +15 -15
- package/dist/ui/components/ViewToggle.style.css +4 -4
- package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
- package/dist/ui/forms/AddAnotherToggle.js +56 -0
- package/dist/ui/forms/ColorPickerField.js +15 -2
- package/dist/ui/forms/FieldCheckbox.js +15 -2
- package/dist/ui/forms/FieldInput.d.ts +22 -1
- package/dist/ui/forms/FieldInput.js +19 -5
- package/dist/ui/forms/FieldLabel.d.ts +35 -0
- package/dist/ui/forms/FieldLabel.js +70 -0
- package/dist/ui/forms/FieldSelect.d.ts +4 -1
- package/dist/ui/forms/FieldSelect.js +27 -5
- package/dist/ui/forms/FieldTextarea.d.ts +4 -1
- package/dist/ui/forms/FieldTextarea.js +22 -5
- package/dist/ui/forms/FormShell.d.ts +17 -1
- package/dist/ui/forms/FormShell.js +36 -2
- package/dist/ui/forms/FormShell.style.css +41 -33
- package/dist/ui/forms/index.d.ts +5 -1
- package/dist/ui/forms/index.js +2 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/utils/id-generators.d.ts +80 -0
- package/dist/utils/id-generators.js +93 -0
- package/dist/utils/search-txt-builders.d.ts +12 -0
- package/dist/utils/search-txt-builders.js +17 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +106 -5
- package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
- package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
- package/dist/features/loyalty/api/balance/route.d.ts +0 -12
- package/dist/features/loyalty/api/balance/route.js +0 -41
- package/dist/features/loyalty/api/earn/route.d.ts +0 -12
- package/dist/features/loyalty/api/earn/route.js +0 -38
- package/dist/features/loyalty/api/history/route.d.ts +0 -12
- package/dist/features/loyalty/api/history/route.js +0 -58
- package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
- package/dist/features/loyalty/api/redeem/route.js +0 -38
- package/dist/features/loyalty/columns/index.d.ts +0 -17
- package/dist/features/loyalty/columns/index.js +0 -40
- package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
- package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
- package/dist/features/loyalty/components/index.d.ts +0 -1
- package/dist/features/loyalty/components/index.js +0 -1
- package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
- package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
- package/dist/features/loyalty/index.d.ts +0 -6
- package/dist/features/loyalty/index.js +0 -6
- package/dist/features/loyalty/manifest.d.ts +0 -2
- package/dist/features/loyalty/manifest.js +0 -16
- package/dist/features/loyalty/messages/en.json +0 -19
- package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
- package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
- package/dist/features/loyalty/schemas/index.d.ts +0 -85
- package/dist/features/loyalty/schemas/index.js +0 -40
- package/dist/features/loyalty/server.d.ts +0 -6
- package/dist/features/loyalty/server.js +0 -6
- package/dist/features/loyalty/types/index.d.ts +0 -31
- package/dist/features/loyalty/types/index.js +0 -1
- package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
- package/dist/features/loyalty/types/loyalty-config.js +0 -1
- package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
- package/dist/features/loyalty/types/loyalty-math.js +0 -27
- package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
- package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
|
@@ -9,9 +9,15 @@ import { PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from
|
|
|
9
9
|
import { CustomSectionsEditor } from "./CustomSectionsEditor.js";
|
|
10
10
|
import { SublistingCategorySelect } from "./SublistingCategorySelect.js";
|
|
11
11
|
import { ProductFeaturesSelector } from "./ProductFeaturesSelector.js";
|
|
12
|
+
// The one `ProductImage[] | string[]` → `string[]` normaliser. Value import,
|
|
13
|
+
// not a type: the gallery needs it at runtime (see `galleryImages` below).
|
|
14
|
+
import { productImageUrls } from "../schemas/firestore.js";
|
|
12
15
|
import { ImageUpload, MediaUploadField, MediaUploadList, } from "../../media/index.js";
|
|
13
16
|
import { useMediaUpload } from "../../media/index.js";
|
|
14
17
|
import { resolveDate } from "../../../utils/date.formatter.js";
|
|
18
|
+
// Read-only here: the inputs that WROTE this field are gone — see the Physical
|
|
19
|
+
// Inventory block for why three free-text boxes could not be made consistent.
|
|
20
|
+
import { formatPhysicalLocation } from "../../../utils/physical-location.js";
|
|
15
21
|
import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/offers/config.js";
|
|
16
22
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
|
|
17
23
|
import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags.js";
|
|
@@ -68,13 +74,36 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
|
|
|
68
74
|
const update = (partial) => {
|
|
69
75
|
onChange({ ...product, ...partial });
|
|
70
76
|
};
|
|
77
|
+
/*
|
|
78
|
+
* The legacy free-text location, if this listing carries one. Shown so the
|
|
79
|
+
* value is not invisible while it still exists; there is no input for it any
|
|
80
|
+
* more. `formatPhysicalLocation` returns `undefined` when all three parts are
|
|
81
|
+
* blank, which is what distinguishes "never set" from "set to nothing".
|
|
82
|
+
*/
|
|
83
|
+
const existingLocation = formatPhysicalLocation(product.physicalLocation);
|
|
71
84
|
/*
|
|
72
85
|
* One control for both media kinds — see the matching comment in
|
|
73
|
-
* SellerProductShell's StepMedia.
|
|
74
|
-
*
|
|
86
|
+
* SellerProductShell's StepMedia. This seeds from `images` + `video` and
|
|
87
|
+
* splits them again on write.
|
|
88
|
+
*
|
|
89
|
+
* 🛑 `images` IS NOT `string[]` AT RUNTIME, whatever the type says. This
|
|
90
|
+
* comment used to assert "the document keeps `images: string[]`" and the
|
|
91
|
+
* line below mapped each entry as a URL — but `ProductDocument.images` has
|
|
92
|
+
* been `ProductImage[]` (objects) since A6, and the editors seed this form
|
|
93
|
+
* by CASTING the raw API document to `Partial<ProductItem>`
|
|
94
|
+
* (`AdminProductEditorView:231`). The cast is what makes the compiler agree
|
|
95
|
+
* with a shape the data does not have.
|
|
96
|
+
*
|
|
97
|
+
* The result was `{ url: { url: "/media/…" }, type: "image" }`, so the
|
|
98
|
+
* gallery tile handed `MediaImage` an object, `resolveMediaUrl` returned
|
|
99
|
+
* undefined for a non-string, and the tile rendered the 📦 placeholder with
|
|
100
|
+
* no network request at all.
|
|
101
|
+
*
|
|
102
|
+
* `productImageUrls` is the one normaliser and accepts both shapes, so this
|
|
103
|
+
* is correct whether the seeder passed documents or legacy strings.
|
|
75
104
|
*/
|
|
76
105
|
const galleryImages = [
|
|
77
|
-
...(product.images
|
|
106
|
+
...productImageUrls(product.images).map((url) => ({ url, type: "image" })),
|
|
78
107
|
...(product.video?.url
|
|
79
108
|
? [{
|
|
80
109
|
url: product.video.url,
|
|
@@ -123,7 +152,7 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
|
|
|
123
152
|
content: product.description || "",
|
|
124
153
|
onChange: (value) => update({ description: value }),
|
|
125
154
|
editable: !isReadonly,
|
|
126
|
-
}) })) : (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: t("formDescription") }), _jsx(RichTextEditor, { value: normalizeRichTextHtml(product.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, placeholder: "Enter product description" }), _jsx(Text, { size: "xs", color: "muted", children: "Rich text is supported for product descriptions." })] })), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsx(_Fragment, { children: renderCategorySelector({
|
|
155
|
+
}) })) : (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: t("formDescription") }), _jsx(RichTextEditor, { ariaLabel: t("formDescription"), value: normalizeRichTextHtml(product.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, placeholder: "Enter product description" }), _jsx(Text, { size: "xs", color: "muted", children: "Rich text is supported for product descriptions." })] })), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsx(_Fragment, { children: renderCategorySelector({
|
|
127
156
|
label: t("formCategory"),
|
|
128
157
|
value: (product.categorySlugs?.[0] ?? product.category) || "",
|
|
129
158
|
onChange: (value) => update({ categorySlugs: value ? [value] : [] }),
|
|
@@ -284,23 +313,5 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
|
|
|
284
313
|
disabled: isReadonly,
|
|
285
314
|
}), !isAuctionListing(product) && renderGroupSettings?.(product), _jsx(ProductFeaturesSelector, { value: product.features ?? [], onChange: (features) => update({ features }), productType: LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE[normalizeListingType(product)], storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Custom Sections" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsxs(Div, { children: [_jsx(Toggle, { checked: !isFinalSale(product), onChange: (checked) => update({ finalSale: !checked }), disabled: isReadonly, label: "Accept change-of-mind returns" }), _jsx(Text, { className: "mt-1", size: "xs", color: "muted", children: isFinalSale(product)
|
|
286
315
|
? "Final sale (default). Not-received, damaged, wrong-item, not-as-described and counterfeit claims are still accepted."
|
|
287
|
-
: "Buyers can return this listing within the platform return window for any reason." })] }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }),
|
|
288
|
-
physicalLocation: {
|
|
289
|
-
zone: v,
|
|
290
|
-
shelf: product.physicalLocation?.shelf ?? "",
|
|
291
|
-
bin: product.physicalLocation?.bin ?? "",
|
|
292
|
-
},
|
|
293
|
-
}), disabled: isReadonly }), _jsx(FormField, { name: "physicalLocation.shelf", label: "Shelf", type: "text", placeholder: "e.g. 3", value: product.physicalLocation?.shelf ?? "", onChange: (v) => update({
|
|
294
|
-
physicalLocation: {
|
|
295
|
-
zone: product.physicalLocation?.zone ?? "",
|
|
296
|
-
shelf: v,
|
|
297
|
-
bin: product.physicalLocation?.bin ?? "",
|
|
298
|
-
},
|
|
299
|
-
}), disabled: isReadonly }), _jsx(FormField, { name: "physicalLocation.bin", label: "Bin", type: "text", placeholder: "e.g. Blue", value: product.physicalLocation?.bin ?? "", onChange: (v) => update({
|
|
300
|
-
physicalLocation: {
|
|
301
|
-
zone: product.physicalLocation?.zone ?? "",
|
|
302
|
-
shelf: product.physicalLocation?.shelf ?? "",
|
|
303
|
-
bin: v,
|
|
304
|
-
},
|
|
305
|
-
}), disabled: isReadonly })] })] }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
|
|
316
|
+
: "Buyers can return this listing within the platform return window for any reason." })] }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), existingLocation ? (_jsxs(Text, { className: "mb-3", color: "muted", size: "xs", children: ["Legacy location on this listing: ", _jsx("strong", { children: existingLocation }), ". It is still printed on labels, but it is free text with no box behind it \u2014 place this item in a storage box to replace it."] })) : (_jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Where this item is stored is recorded by placing it in a storage box, not typed here \u2014 a placement has a quantity, and one product can sit in two boxes. Use the receive station or the box's own page." }))] }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
|
|
306
317
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import Link from "next/link.js";
|
|
5
|
-
import { useRouter } from "next/navigation.js";
|
|
6
|
-
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
7
5
|
import { BaseListingCard, Button, Div, GatedPrice, Grid, PriceDisplay, PricesOnly, Row, Span, Stack, Text } from "../../../ui/index.js";
|
|
8
6
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
9
7
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
@@ -11,13 +9,14 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
|
|
|
11
9
|
import { useLongPress } from "../../../react/hooks/useLongPress.js";
|
|
12
10
|
import { CardStoreLine } from "./CardStoreLine.js";
|
|
13
11
|
import { isFinalSale } from "../constants/final-sale.js";
|
|
12
|
+
import { CARD_BADGE, resolveCardBadges } from "../constants/card-badges.js";
|
|
14
13
|
import { FeatureBadgeList } from "./FeatureBadge.js";
|
|
15
14
|
import { useProductFeatures } from "./ProductFeaturesContext.js";
|
|
16
15
|
import { PRODUCT_FEATURE_CARD_MAX_VISIBLE } from "../constants/product-features.constants.js";
|
|
17
16
|
import { normalizeListingType } from "../utils/listing-type.js";
|
|
18
17
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
19
18
|
import { WishlistHeartButton } from "../../wishlist/components/WishlistHeartButton.js";
|
|
20
|
-
import {
|
|
19
|
+
import { RecordOwnerQuickActions } from "../../admin/components/RecordOwnerQuickActions.js";
|
|
21
20
|
const __P = {
|
|
22
21
|
p3: "p-[var(--appkit-space-3)]",
|
|
23
22
|
};
|
|
@@ -25,22 +24,6 @@ const __O = {
|
|
|
25
24
|
hidden: "overflow-hidden",
|
|
26
25
|
};
|
|
27
26
|
const CLS_BADGE_NEW = "rounded-full bg-error-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] text-error-on-solid shadow-sm";
|
|
28
|
-
const CLS_BADGE_SALE = "rounded-full bg-success-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-success-on-solid shadow-sm";
|
|
29
|
-
const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
|
|
30
|
-
const CLS_BADGE_LIMITED = "rounded-full bg-info-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-info-on-solid shadow-sm";
|
|
31
|
-
// Overlay badge sitting on the product photo, so the pairing is `-solid` +
|
|
32
|
-
// `-on-solid` (Root Cause #67) — never `text-white` on a status colour, which
|
|
33
|
-
// inverts with the theme. `warning` because success/info/error are each already
|
|
34
|
-
// taken by a sibling pill in this same stack.
|
|
35
|
-
const CLS_BADGE_OFFERABLE = "rounded-full bg-warning-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-warning-on-solid shadow-sm";
|
|
36
|
-
/*
|
|
37
|
-
* Final sale. A fixed dark scrim with white ink, following CLS_BADGE_TRENDING's
|
|
38
|
-
* precedent rather than a `-surface` token: this sits on top of the product
|
|
39
|
-
* PHOTO, where a theme-relative tint is a near-white fill that vanishes against
|
|
40
|
-
* a light image (Root Cause #67). Not `warning-solid`, which "Taking Offers"
|
|
41
|
-
* already owns — two amber pills stacked would read as one control.
|
|
42
|
-
*/
|
|
43
|
-
const CLS_BADGE_FINAL_SALE = "rounded-full bg-zinc-900/90 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
|
|
44
27
|
const CLS_STAR = "text-[11px] text-warning";
|
|
45
28
|
const CLS_DISCOUNT_TEXT = "mt-1 text-[11px] font-semibold text-error";
|
|
46
29
|
const CLS_DISCOUNT_TEXT_BARE = "text-[10px] text-error";
|
|
@@ -51,11 +34,65 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
51
34
|
? Math.round(((product.originalPrice - product.price) / product.originalPrice) *
|
|
52
35
|
100)
|
|
53
36
|
: null;
|
|
54
|
-
const router = useRouter();
|
|
55
37
|
const listingPlugin = pluginFor(normalizeListingType(product));
|
|
56
38
|
const typeBadge = listingPlugin.badge
|
|
57
39
|
? { label: listingPlugin.badge.label, cls: listingPlugin.badge.className }
|
|
58
40
|
: null;
|
|
41
|
+
/*
|
|
42
|
+
* The badge corner, resolved once. Two candidates, ordered and capped by
|
|
43
|
+
* `card-badges.ts` — see the stack below for what the other five badges were
|
|
44
|
+
* and where they went.
|
|
45
|
+
*/
|
|
46
|
+
const cardBadges = resolveCardBadges([
|
|
47
|
+
discount
|
|
48
|
+
? {
|
|
49
|
+
id: CARD_BADGE.DISCOUNT,
|
|
50
|
+
label: `-${discount}%`,
|
|
51
|
+
className: CLS_BADGE_NEW,
|
|
52
|
+
title: `${discount}% off the original price`,
|
|
53
|
+
}
|
|
54
|
+
: null,
|
|
55
|
+
typeBadge
|
|
56
|
+
? {
|
|
57
|
+
id: CARD_BADGE.LISTING_TYPE,
|
|
58
|
+
label: typeBadge.label,
|
|
59
|
+
// The plugin's own class, plus the shared pill geometry the inline
|
|
60
|
+
// version used to spell out at the call site.
|
|
61
|
+
className: `rounded-full px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] shadow-sm ${typeBadge.cls}`,
|
|
62
|
+
}
|
|
63
|
+
: null,
|
|
64
|
+
]);
|
|
65
|
+
/*
|
|
66
|
+
* The five demoted badges, as plain words on the metadata line.
|
|
67
|
+
*
|
|
68
|
+
* 🛑 DEMOTED, NOT DELETED. Each states something true that a buyer needs —
|
|
69
|
+
* "Final Sale" is a refund-policy warning and dropping it silently would be
|
|
70
|
+
* a worse defect than crowding the image. But a 10px pill over a photograph
|
|
71
|
+
* is the wrong place to say it: a shopper scanning a grid cannot act on any
|
|
72
|
+
* of them, and five of them hid the two they could act on.
|
|
73
|
+
*
|
|
74
|
+
* `isFinalSale(product)` rather than `product.finalSale` — absent MEANS
|
|
75
|
+
* final sale, so reading the raw field would leave every listing created
|
|
76
|
+
* before it existed unlabelled while still being final sale at the refund
|
|
77
|
+
* gate.
|
|
78
|
+
*/
|
|
79
|
+
const cardNotes = [
|
|
80
|
+
product.partOfBundleIds && product.partOfBundleIds.length > 0
|
|
81
|
+
? product.partOfBundleTitles?.[0]
|
|
82
|
+
? `In bundle: ${product.partOfBundleTitles[0]}`
|
|
83
|
+
: "In a bundle"
|
|
84
|
+
: null,
|
|
85
|
+
product.groupId
|
|
86
|
+
? product.isGroupParent
|
|
87
|
+
? "Set parent"
|
|
88
|
+
: product.groupTitle
|
|
89
|
+
? `Part of set: ${product.groupTitle}`
|
|
90
|
+
: "Part of a set"
|
|
91
|
+
: null,
|
|
92
|
+
product.allowOffers ? "Taking offers" : null,
|
|
93
|
+
isFinalSale(product) ? "Final sale" : null,
|
|
94
|
+
product.sublistingCategoryId ? "Has variants" : null,
|
|
95
|
+
].filter((n) => n != null);
|
|
59
96
|
const longPress = useLongPress(() => onSelect?.(product.id));
|
|
60
97
|
const featuresList = useProductFeatures();
|
|
61
98
|
const handleCardClick = selectionMode
|
|
@@ -78,36 +115,13 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
78
115
|
className,
|
|
79
116
|
]
|
|
80
117
|
.filter(Boolean)
|
|
81
|
-
.join(" "), children: [_jsxs(Div, { className: `relative ${__O.hidden} aspect-square`, surface: "subtle", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { surface: "muted", className: "h-full w-full", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-30", size: "4xl", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsx(Stack, { className: `absolute top-2 ${onSelect ? "left-10" : "left-2"}`, gap: "xs", children: !selectionMode &&
|
|
82
|
-
|
|
83
|
-
|
|
118
|
+
.join(" "), children: [_jsxs(Div, { className: `relative ${__O.hidden} aspect-square`, surface: "subtle", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { surface: "muted", className: "h-full w-full", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-30", size: "4xl", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsx(Stack, { className: `absolute top-2 ${onSelect ? "left-10" : "left-2"}`, gap: "xs", children: !selectionMode &&
|
|
119
|
+
!isSelected &&
|
|
120
|
+
cardBadges.map((b) => (_jsx(Span, { weight: "bold", className: b.className, title: b.title, children: b.label }, b.id))) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(WishlistHeartButton, { size: "card", variant: "overlay", inWishlist: !!isWishlisted, addLabel: "Add to wishlist", removeLabel: "Remove from wishlist", className: "absolute right-2 top-2", onToggle: (e) => {
|
|
84
121
|
e.stopPropagation();
|
|
85
122
|
e.preventDefault();
|
|
86
123
|
onAddToWishlist(product.id);
|
|
87
|
-
} }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3} pt-[0.625rem]`, children: [_jsx(Text, { className: `leading-snug`, truncate: 2, size: "sm", weight: "semibold", color: "primary", children: product.title }), (() => {
|
|
88
|
-
const categories = Array.isArray(product.categoryNames)
|
|
89
|
-
? product.categoryNames.filter(Boolean)
|
|
90
|
-
: product.categoryName
|
|
91
|
-
? [product.categoryName]
|
|
92
|
-
: [];
|
|
93
|
-
if (!categories.length && !product.brand)
|
|
94
|
-
return null;
|
|
95
|
-
const MAX_CHIPS = 3;
|
|
96
|
-
const visible = categories.slice(0, MAX_CHIPS);
|
|
97
|
-
const overflow = Math.max(0, categories.length - MAX_CHIPS);
|
|
98
|
-
return (_jsxs(Row, { className: "mt-1", gap: "xs", wrap: true, children: [visible.map((name) => (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", className: "gap-[0.125rem] text-[10px]", rounded: "full", surface: "subtle", color: "muted", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), name] }, name))), overflow > 0 && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", weight: "medium", className: "text-[10px]", rounded: "full", surface: "subtle", color: "muted", title: categories.slice(MAX_CHIPS).join(", "), children: ["+", overflow] })), product.brand && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", gap: "2xs", className: `text-[10px] ${product.brandSlug ? "cursor-pointer hover:underline" : ""}`, rounded: "full", surface: "subtle", color: "muted", ...(product.brandSlug
|
|
99
|
-
? {
|
|
100
|
-
onClick: (e) => {
|
|
101
|
-
// The whole card is already wrapped in a <Link> — a nested
|
|
102
|
-
// <a> here would be invalid HTML, so this navigates via
|
|
103
|
-
// stopPropagation + router.push instead of a real Link.
|
|
104
|
-
e.preventDefault();
|
|
105
|
-
e.stopPropagation();
|
|
106
|
-
router.push(String(ROUTES.PUBLIC.BRAND_DETAIL(product.brandSlug)));
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
: {}), children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] }));
|
|
110
|
-
})(), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), product.rating !== undefined && (_jsxs(Row, { className: "mt-1", gap: "xs", children: [_jsx(Span, { className: CLS_STAR, children: "\u2605" }), _jsxs(Span, { className: "text-[11px]", color: "muted", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto", padding: "t-xs", children: [_jsx(PriceDisplay, { amount: product.price, originalAmount: product.originalPrice, currency: getDefaultCurrency(), variant: "compact" }), (() => {
|
|
124
|
+
} }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3} pt-[0.625rem]`, children: [_jsx(Text, { className: `leading-snug`, truncate: 2, size: "sm", weight: "semibold", color: "primary", children: product.title }), product.brand && (_jsx(Text, { className: "mt-1", size: "xs", color: "muted", truncate: 1, children: product.brand })), cardNotes.length > 0 && (_jsx(Text, { className: "mt-1", size: "xs", color: "muted", truncate: 1, title: cardNotes.join(" · "), children: cardNotes.join(" · ") })), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), product.rating !== undefined && (product.reviewCount ?? 0) > 0 && (_jsxs(Row, { className: "mt-1", gap: "xs", children: [_jsx(Span, { className: CLS_STAR, children: "\u2605" }), _jsxs(Span, { className: "text-[11px]", color: "muted", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto", padding: "t-xs", children: [_jsx(PriceDisplay, { amount: product.price, originalAmount: product.originalPrice, currency: getDefaultCurrency(), variant: "compact" }), (() => {
|
|
111
125
|
const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
|
|
112
126
|
if (stock === undefined || !listingPlugin.showsStockQuantity)
|
|
113
127
|
return null;
|
|
@@ -116,15 +130,15 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
116
130
|
}
|
|
117
131
|
const low = stock <= 5;
|
|
118
132
|
return (_jsx(Text, { className: `mt-1 text-[11px] ${low ? "text-warning" : "text-[var(--appkit-color-text-faint)]"}`, weight: "medium", children: low ? `Only ${stock} left` : `${stock} in stock` }));
|
|
119
|
-
})(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { layout: "grid", gap: "1", className: `mt-2 ${onBuyNow && onAddToCart ? "grid-cols-2" : "grid-cols-1"}`, children: [onBuyNow && (
|
|
133
|
+
})(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { layout: "grid", gap: "1", className: `mt-2 ${onBuyNow && onAddToCart ? "grid-cols-2" : "grid-cols-1"}`, children: [onBuyNow && (_jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: (e) => {
|
|
120
134
|
e.stopPropagation();
|
|
121
135
|
e.preventDefault();
|
|
122
136
|
onBuyNow(product);
|
|
123
|
-
}, className: "justify-center text-[length:var(--appkit-text-xs)] btn-glow", children:
|
|
137
|
+
}, className: "justify-center text-[length:var(--appkit-text-xs)] btn-glow", children: "Buy Now" })), onAddToCart && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: (e) => {
|
|
124
138
|
e.stopPropagation();
|
|
125
139
|
e.preventDefault();
|
|
126
140
|
onAddToCart(product);
|
|
127
|
-
}, className: "justify-center text-[length:var(--appkit-text-xs)]", children:
|
|
141
|
+
}, className: "justify-center text-[length:var(--appkit-text-xs)]", children: "Cart" }))] }))] })] })] }));
|
|
128
142
|
const wrapped = href && !selectionMode ? (_jsx(Link, { href: href, className: "block h-full", children: cardBody })) : (cardBody);
|
|
129
143
|
/*
|
|
130
144
|
* 🛑 Default, not opt-in — nothing in either repo ever passed
|
|
@@ -138,29 +152,39 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
138
152
|
* editor. `storeId` is the store SLUG, which is what `SessionUser.storeId`
|
|
139
153
|
* holds — the component renders nothing for anyone who owns neither.
|
|
140
154
|
*/
|
|
141
|
-
const ownerActions = renderOwnerActions ? (renderOwnerActions()) : product.id ? (_jsx(
|
|
155
|
+
const ownerActions = renderOwnerActions ? (renderOwnerActions()) : product.id ? (_jsx(RecordOwnerQuickActions, { resource: "PRODUCT", recordId: String(product.id), storeId: product.storeId ? String(product.storeId) : undefined, recordLabel: product.title, placement: "card" })) : null;
|
|
142
156
|
// Selection is still suppressed: a bulk-select gesture and a per-record edit
|
|
143
157
|
// are different intents, and offering both at once invites the wrong one.
|
|
144
158
|
if (!ownerActions || selectionMode || isSelected)
|
|
145
159
|
return wrapped;
|
|
146
160
|
/*
|
|
147
|
-
* 🛑 OUTSIDE
|
|
161
|
+
* 🛑 STILL OUTSIDE THE ANCHOR, as a sibling — never inside `cardBody`.
|
|
148
162
|
*
|
|
149
163
|
* This card wraps its entire body in a `<Link>` whenever `href` is set, and
|
|
150
164
|
* `InteractiveProductCard` wraps it in another one. A `<button>` inside an
|
|
151
165
|
* `<a>` is invalid HTML that browsers resolve by closing the anchor early,
|
|
152
166
|
* which breaks the CARD's navigation rather than just the button's —
|
|
153
|
-
* stopPropagation stops the click but cannot fix the parse.
|
|
167
|
+
* stopPropagation stops the click but cannot fix the parse. That constraint
|
|
168
|
+
* is unchanged and is why this sits here rather than next to the title.
|
|
169
|
+
*
|
|
170
|
+
* 🛑 BUT NO LONGER ABSOLUTE, AND NO LONGER ON THE PHOTO.
|
|
171
|
+
*
|
|
172
|
+
* It used to be `absolute right-2 top-14 z-20`, chosen by elimination — the
|
|
173
|
+
* wishlist heart owns `top-2 right-2`, the checkbox and badges own top-left,
|
|
174
|
+
* `IconOverlay` owns bottom-right — which left the middle of the product
|
|
175
|
+
* photograph as the only free corner. So every seller-visible card had three
|
|
176
|
+
* icons sitting on the artwork, and on the pre-order card the same logic put
|
|
177
|
+
* them on top of the "Reserve now" button.
|
|
154
178
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* 3.5rem + 44px and the toolbar lands on the photo.
|
|
179
|
+
* In normal flow at the FOOT of the card it collides with nothing by
|
|
180
|
+
* construction: no corner budget to reason about, no overlap with a CTA, and
|
|
181
|
+
* the same position on every card type. The toolbar supplies its own box, so
|
|
182
|
+
* it reads as a distinct owner strip rather than as part of the listing.
|
|
160
183
|
*
|
|
161
|
-
* `
|
|
184
|
+
* `justify-end` keeps it on the side the eye already uses for card actions;
|
|
185
|
+
* `pt-0` because the card body's own bottom padding is the gap.
|
|
162
186
|
*/
|
|
163
|
-
return (_jsxs(Div, { className: "
|
|
187
|
+
return (_jsxs(Div, { className: "flex h-full flex-col", children: [wrapped, _jsx(Div, { className: "flex justify-end px-[var(--appkit-space-3)] pb-[var(--appkit-space-3)] pt-0", children: ownerActions })] }));
|
|
164
188
|
}
|
|
165
189
|
function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
|
|
166
190
|
const discount = product.originalPrice && product.originalPrice > product.price
|
|
@@ -178,7 +202,7 @@ function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
|
|
|
178
202
|
.join(" "), children: [_jsx(Div, { className: `flex-shrink-0 w-16 h-16 sm:w-20 sm:h-20 ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "lg", children: product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "thumbnail" })) : (_jsx(Div, { className: "w-full h-full bg-neutral-200" })) }), _jsxs(Stack, { className: "flex-1 min-w-0 gap-[2px]", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }), (product.categoryName || product.brand) && (_jsx(Span, { className: "text-[11px] truncate", color: "faint", children: [product.categoryName, product.brand].filter(Boolean).join(" · ") })), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId, className: "text-[11px]" }), _jsxs(Row, { className: "mt-0.5", align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { size: "sm", weight: "semibold", className: "text-primary", children: _jsx(GatedPrice, { children: formatCurrency(product.price, getDefaultCurrency()) }) }), discount && (
|
|
179
203
|
// The discount % is derived from price vs originalPrice, so it is a
|
|
180
204
|
// money disclosure even though it carries no currency symbol.
|
|
181
|
-
_jsx(PricesOnly, { children: _jsxs(Span, { weight: "bold", className: CLS_DISCOUNT_TEXT_BARE, children: ["-", discount, "%"] }) })), product.rating !== undefined && (_jsxs(Span, { layout: "flex", gap: "2xs", className: "text-[11px]", color: "faint", children: [_jsx(Span, { className: CLS_STAR_BARE, children: "\u2605" }), product.rating.toFixed(1)] })), (() => {
|
|
205
|
+
_jsx(PricesOnly, { children: _jsxs(Span, { weight: "bold", className: CLS_DISCOUNT_TEXT_BARE, children: ["-", discount, "%"] }) })), product.rating !== undefined && (product.reviewCount ?? 0) > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", className: "text-[11px]", color: "faint", children: [_jsx(Span, { className: CLS_STAR_BARE, children: "\u2605" }), product.rating.toFixed(1)] })), (() => {
|
|
182
206
|
const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
|
|
183
207
|
if (stock === undefined)
|
|
184
208
|
return null;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a product card may say in its badge corner, and in what order.
|
|
3
|
+
*
|
|
4
|
+
* ── THE DEFECT THIS REPLACES ────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* `ProductCard` rendered SEVEN independent badges into one absolutely-
|
|
7
|
+
* positioned stack, each unconditionally shown when its own condition held:
|
|
8
|
+
*
|
|
9
|
+
* -20% · Auction · Bundled · In Set · Taking Offers · Final Sale
|
|
10
|
+
* · Has Variants
|
|
11
|
+
*
|
|
12
|
+
* None of them knew about the others. A discounted auction that is a bundled
|
|
13
|
+
* set member with offers enabled, final sale and variants renders all seven,
|
|
14
|
+
* stacked top-left, covering the product photograph the card exists to show.
|
|
15
|
+
* Nothing is wrong with any one of those badges; the defect is that nobody
|
|
16
|
+
* decided which matters.
|
|
17
|
+
*
|
|
18
|
+
* ── THE RULE ────────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* 🛑 A BADGE EARNS ITS PLACE BY CHANGING WHETHER THE SHOPPER CLICKS. That is
|
|
21
|
+
* the whole test, and it sorts the seven cleanly:
|
|
22
|
+
*
|
|
23
|
+
* KEEPS ITS PLACE — changes the decision to click
|
|
24
|
+
* · listing TYPE: an auction and a pre-order are different transactions
|
|
25
|
+
* with different CTAs. Not knowing is a wasted click.
|
|
26
|
+
* · DISCOUNT: the single commercial reason to look at this card first.
|
|
27
|
+
*
|
|
28
|
+
* LOSES ITS PLACE — matters when deciding to BUY, not to click
|
|
29
|
+
* · Final Sale, Taking Offers, Bundled, In Set, Has Variants
|
|
30
|
+
*
|
|
31
|
+
* The second group is not deleted — it moves to the detail page, where there
|
|
32
|
+
* is room to state it in words rather than a 10px pill. A badge a shopper
|
|
33
|
+
* cannot act on from a grid is noise, and noise hides the badge they can act
|
|
34
|
+
* on, which is the actual cost of having seven.
|
|
35
|
+
*
|
|
36
|
+
* 🛑 `MAX_CARD_BADGES = 2` IS THE MECHANISM, and it has to exist separately
|
|
37
|
+
* from the precedence list. A precedence with no cap is still seven badges in
|
|
38
|
+
* a known order; the cap is what makes adding an eighth a design decision
|
|
39
|
+
* instead of a silent append. An overflow count ("+3") was considered and
|
|
40
|
+
* rejected — it is itself a badge, and one that says nothing.
|
|
41
|
+
*
|
|
42
|
+
* ── WHY A MODULE AND NOT A `.slice(0, 2)` AT THE CALL SITE ─────────────────
|
|
43
|
+
*
|
|
44
|
+
* Three card components render these (`ProductCard`, its list layout, and the
|
|
45
|
+
* per-type marketplace cards), and a cap applied at one of them is the shape
|
|
46
|
+
* this codebase has paid for repeatedly: `unitPriceFor` documented itself as
|
|
47
|
+
* "the single source of truth" and was then hand-copied into four more places,
|
|
48
|
+
* one of which never learned about locked prices and charged the list price
|
|
49
|
+
* for a negotiated sale (Root Cause #75).
|
|
50
|
+
*
|
|
51
|
+
* @tag domain:products
|
|
52
|
+
* @tag layer:shared
|
|
53
|
+
* @tag access:isomorphic
|
|
54
|
+
*/
|
|
55
|
+
/** Stable ids, so a consumer can reason about precedence without string-matching. */
|
|
56
|
+
export declare const CARD_BADGE: {
|
|
57
|
+
readonly DISCOUNT: "discount";
|
|
58
|
+
readonly LISTING_TYPE: "listing-type";
|
|
59
|
+
};
|
|
60
|
+
export type CardBadgeId = (typeof CARD_BADGE)[keyof typeof CARD_BADGE];
|
|
61
|
+
export interface CardBadge {
|
|
62
|
+
id: CardBadgeId;
|
|
63
|
+
label: string;
|
|
64
|
+
/** Resolved by the caller from the listing-type plugin, or a local constant. */
|
|
65
|
+
className: string;
|
|
66
|
+
/** Long form, for the `title` attribute. */
|
|
67
|
+
title?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 🛑 THE CAP. Two is not a round number — it is one commercial signal
|
|
71
|
+
* (discount) plus one transactional one (type), which are the only two
|
|
72
|
+
* questions a shopper answers before clicking. A third badge would have to
|
|
73
|
+
* displace one of those to earn its place.
|
|
74
|
+
*/
|
|
75
|
+
export declare const MAX_CARD_BADGES = 2;
|
|
76
|
+
/**
|
|
77
|
+
* Precedence, highest first.
|
|
78
|
+
*
|
|
79
|
+
* 🛑 DISCOUNT OUTRANKS TYPE, deliberately, and this is the one judgement call
|
|
80
|
+
* in the file. Type is what the card IS; discount is why this card rather than
|
|
81
|
+
* the one beside it. In a grid the shopper is comparing, so the comparative
|
|
82
|
+
* signal goes first — and at the cap of two both are shown anyway, so the
|
|
83
|
+
* order only decides which is read first.
|
|
84
|
+
*/
|
|
85
|
+
export declare const CARD_BADGE_PRECEDENCE: readonly CardBadgeId[];
|
|
86
|
+
/**
|
|
87
|
+
* Sort candidates by precedence and cap them.
|
|
88
|
+
*
|
|
89
|
+
* Takes already-resolved badges rather than a product, so this module needs no
|
|
90
|
+
* import of the listing-type registry or the product schema — it stays a pure
|
|
91
|
+
* ordering decision, which is the only thing it should be opinionated about.
|
|
92
|
+
*
|
|
93
|
+
* An id absent from `CARD_BADGE_PRECEDENCE` sorts LAST rather than being
|
|
94
|
+
* dropped: silently discarding a badge a caller built would be the harder
|
|
95
|
+
* failure to find, and the cap removes it anyway if it does not fit.
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveCardBadges(candidates: readonly (CardBadge | null | undefined)[]): CardBadge[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a product card may say in its badge corner, and in what order.
|
|
3
|
+
*
|
|
4
|
+
* ── THE DEFECT THIS REPLACES ────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* `ProductCard` rendered SEVEN independent badges into one absolutely-
|
|
7
|
+
* positioned stack, each unconditionally shown when its own condition held:
|
|
8
|
+
*
|
|
9
|
+
* -20% · Auction · Bundled · In Set · Taking Offers · Final Sale
|
|
10
|
+
* · Has Variants
|
|
11
|
+
*
|
|
12
|
+
* None of them knew about the others. A discounted auction that is a bundled
|
|
13
|
+
* set member with offers enabled, final sale and variants renders all seven,
|
|
14
|
+
* stacked top-left, covering the product photograph the card exists to show.
|
|
15
|
+
* Nothing is wrong with any one of those badges; the defect is that nobody
|
|
16
|
+
* decided which matters.
|
|
17
|
+
*
|
|
18
|
+
* ── THE RULE ────────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* 🛑 A BADGE EARNS ITS PLACE BY CHANGING WHETHER THE SHOPPER CLICKS. That is
|
|
21
|
+
* the whole test, and it sorts the seven cleanly:
|
|
22
|
+
*
|
|
23
|
+
* KEEPS ITS PLACE — changes the decision to click
|
|
24
|
+
* · listing TYPE: an auction and a pre-order are different transactions
|
|
25
|
+
* with different CTAs. Not knowing is a wasted click.
|
|
26
|
+
* · DISCOUNT: the single commercial reason to look at this card first.
|
|
27
|
+
*
|
|
28
|
+
* LOSES ITS PLACE — matters when deciding to BUY, not to click
|
|
29
|
+
* · Final Sale, Taking Offers, Bundled, In Set, Has Variants
|
|
30
|
+
*
|
|
31
|
+
* The second group is not deleted — it moves to the detail page, where there
|
|
32
|
+
* is room to state it in words rather than a 10px pill. A badge a shopper
|
|
33
|
+
* cannot act on from a grid is noise, and noise hides the badge they can act
|
|
34
|
+
* on, which is the actual cost of having seven.
|
|
35
|
+
*
|
|
36
|
+
* 🛑 `MAX_CARD_BADGES = 2` IS THE MECHANISM, and it has to exist separately
|
|
37
|
+
* from the precedence list. A precedence with no cap is still seven badges in
|
|
38
|
+
* a known order; the cap is what makes adding an eighth a design decision
|
|
39
|
+
* instead of a silent append. An overflow count ("+3") was considered and
|
|
40
|
+
* rejected — it is itself a badge, and one that says nothing.
|
|
41
|
+
*
|
|
42
|
+
* ── WHY A MODULE AND NOT A `.slice(0, 2)` AT THE CALL SITE ─────────────────
|
|
43
|
+
*
|
|
44
|
+
* Three card components render these (`ProductCard`, its list layout, and the
|
|
45
|
+
* per-type marketplace cards), and a cap applied at one of them is the shape
|
|
46
|
+
* this codebase has paid for repeatedly: `unitPriceFor` documented itself as
|
|
47
|
+
* "the single source of truth" and was then hand-copied into four more places,
|
|
48
|
+
* one of which never learned about locked prices and charged the list price
|
|
49
|
+
* for a negotiated sale (Root Cause #75).
|
|
50
|
+
*
|
|
51
|
+
* @tag domain:products
|
|
52
|
+
* @tag layer:shared
|
|
53
|
+
* @tag access:isomorphic
|
|
54
|
+
*/
|
|
55
|
+
/** Stable ids, so a consumer can reason about precedence without string-matching. */
|
|
56
|
+
export const CARD_BADGE = {
|
|
57
|
+
DISCOUNT: "discount",
|
|
58
|
+
LISTING_TYPE: "listing-type",
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* 🛑 THE CAP. Two is not a round number — it is one commercial signal
|
|
62
|
+
* (discount) plus one transactional one (type), which are the only two
|
|
63
|
+
* questions a shopper answers before clicking. A third badge would have to
|
|
64
|
+
* displace one of those to earn its place.
|
|
65
|
+
*/
|
|
66
|
+
export const MAX_CARD_BADGES = 2;
|
|
67
|
+
/**
|
|
68
|
+
* Precedence, highest first.
|
|
69
|
+
*
|
|
70
|
+
* 🛑 DISCOUNT OUTRANKS TYPE, deliberately, and this is the one judgement call
|
|
71
|
+
* in the file. Type is what the card IS; discount is why this card rather than
|
|
72
|
+
* the one beside it. In a grid the shopper is comparing, so the comparative
|
|
73
|
+
* signal goes first — and at the cap of two both are shown anyway, so the
|
|
74
|
+
* order only decides which is read first.
|
|
75
|
+
*/
|
|
76
|
+
export const CARD_BADGE_PRECEDENCE = [
|
|
77
|
+
CARD_BADGE.DISCOUNT,
|
|
78
|
+
CARD_BADGE.LISTING_TYPE,
|
|
79
|
+
];
|
|
80
|
+
/**
|
|
81
|
+
* Sort candidates by precedence and cap them.
|
|
82
|
+
*
|
|
83
|
+
* Takes already-resolved badges rather than a product, so this module needs no
|
|
84
|
+
* import of the listing-type registry or the product schema — it stays a pure
|
|
85
|
+
* ordering decision, which is the only thing it should be opinionated about.
|
|
86
|
+
*
|
|
87
|
+
* An id absent from `CARD_BADGE_PRECEDENCE` sorts LAST rather than being
|
|
88
|
+
* dropped: silently discarding a badge a caller built would be the harder
|
|
89
|
+
* failure to find, and the cap removes it anyway if it does not fit.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveCardBadges(candidates) {
|
|
92
|
+
const rank = (id) => {
|
|
93
|
+
const i = CARD_BADGE_PRECEDENCE.indexOf(id);
|
|
94
|
+
return i === -1 ? CARD_BADGE_PRECEDENCE.length : i;
|
|
95
|
+
};
|
|
96
|
+
return candidates
|
|
97
|
+
.filter((b) => b != null)
|
|
98
|
+
.sort((a, b) => rank(a.id) - rank(b.id))
|
|
99
|
+
.slice(0, MAX_CARD_BADGES);
|
|
100
|
+
}
|
|
@@ -15,7 +15,7 @@ import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../..
|
|
|
15
15
|
*/
|
|
16
16
|
import { cacheManager } from "../../../core/CacheManager.js";
|
|
17
17
|
import { serverLogger } from "../../../monitoring/index.js";
|
|
18
|
-
import { generateUniqueId, slugify, generateBarcodeId } from "../../../utils/index.js";
|
|
18
|
+
import { generateUniqueId, slugify, generateBarcodeId, generateSku } from "../../../utils/index.js";
|
|
19
19
|
import { parseSearchTxtQuery } from "../../../utils/search-txt.js";
|
|
20
20
|
// The shared search plan/refine the other eleven migrated collections use.
|
|
21
21
|
// `planSearchTxt` carries the `empty` guard that keeps a punctuation-only query
|
|
@@ -941,6 +941,30 @@ export class ProductRepository extends BaseRepository {
|
|
|
941
941
|
}
|
|
942
942
|
});
|
|
943
943
|
const barcodeId = input.barcodeId ?? await generateBarcodeId(id);
|
|
944
|
+
/*
|
|
945
|
+
* The HUMAN key, derived on create beside the machine one.
|
|
946
|
+
*
|
|
947
|
+
* 🛑 Generated here rather than left to the form, because an absent `sku`
|
|
948
|
+
* is what produced "No SKU" on every row of /admin/products and an admin
|
|
949
|
+
* search box promising a field that did not exist. A key that is only
|
|
950
|
+
* present when someone remembered to type it is a key nothing can rely on.
|
|
951
|
+
*
|
|
952
|
+
* Synchronous, unlike `generateBarcodeId` — it is a `Math.imul` hash
|
|
953
|
+
* rather than `crypto.subtle`, specifically so seed rows and form state
|
|
954
|
+
* can call it without threading a promise through every row builder.
|
|
955
|
+
*
|
|
956
|
+
* Reads the DERIVED taxonomy below where it can, but the leaf is not
|
|
957
|
+
* resolved until `deriveTaxonomy` runs, so this uses the inbound value and
|
|
958
|
+
* accepts a slightly less specific token over a second await.
|
|
959
|
+
*/
|
|
960
|
+
const sku = input.sku ??
|
|
961
|
+
generateSku({
|
|
962
|
+
productId: id,
|
|
963
|
+
categorySlug: Array.isArray(input.categorySlugs)
|
|
964
|
+
? input.categorySlugs[0]
|
|
965
|
+
: input.category,
|
|
966
|
+
brand: input.brand,
|
|
967
|
+
});
|
|
944
968
|
// Always derive on create — one extra read per listing created, and it is
|
|
945
969
|
// what makes a UI-created product reachable from its own category page at
|
|
946
970
|
// all. A seed row already carrying the correct chain derives to the same
|
|
@@ -955,6 +979,15 @@ export class ProductRepository extends BaseRepository {
|
|
|
955
979
|
const withTaxonomy = {
|
|
956
980
|
...input,
|
|
957
981
|
...derived,
|
|
982
|
+
/*
|
|
983
|
+
* 🛑 `sku` goes in HERE, not just on `productData` below, and the
|
|
984
|
+
* difference matters: `buildProductSearchTxt(withTaxonomy)` is what
|
|
985
|
+
* builds the stored token list. A `sku` added only to the stored
|
|
986
|
+
* document would be invisible to every search — which is Root Cause
|
|
987
|
+
* #101's shape exactly, a field present on the object and absent from
|
|
988
|
+
* what a query reads.
|
|
989
|
+
*/
|
|
990
|
+
sku,
|
|
958
991
|
...(input.images !== undefined ? { images: toProductImages(input.images) } : {}),
|
|
959
992
|
};
|
|
960
993
|
const productData = {
|