@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
|
@@ -51,7 +51,46 @@ import { serverLogger } from "../../../../../monitoring/index.js";
|
|
|
51
51
|
import { categoriesRepository } from "../../../repository/categories.repository.js";
|
|
52
52
|
import { usedPlaceholders } from "../../../../../_internal/shared/templating/placeholders.js";
|
|
53
53
|
import { getCompositionsForCategory } from "../../../../../_internal/server/features/parts/data.js";
|
|
54
|
+
import { getBeysForCategory } from "../../../../../_internal/server/features/beys/data.js";
|
|
54
55
|
import { PART_BREAKDOWN_SLOT, formatPartBreakdown, stripSlotLine, } from "../../../../parts/utils/part-breakdown.js";
|
|
56
|
+
/**
|
|
57
|
+
* ── The TAXONOMY slots ────────────────────────────────────────────────────
|
|
58
|
+
*
|
|
59
|
+
* Measured across the seeded bodies: 45 `{{title}}` · 21 `{{siteName}}` ·
|
|
60
|
+
* 18 `{{series}}` · 17 `{{category}}` · 13 `{{condition}}` · 6 `{{brand}}` ·
|
|
61
|
+
* 5 `{{partBreakdown}}` · 3 `{{categoryPath}}`. The seller form supplies four
|
|
62
|
+
* of those from the draft it is holding (title, brand, condition, siteName)
|
|
63
|
+
* and this route supplied exactly one (`partBreakdown`) — so
|
|
64
|
+
* **`{{category}}`, `{{categoryPath}}`, `{{series}}` and `{{system}}` could
|
|
65
|
+
* never resolve on any listing**, and the picker reported "4 of 9 slots could
|
|
66
|
+
* not be filled" on every single category, forever.
|
|
67
|
+
*
|
|
68
|
+
* All four are facts about the CATEGORY, not the draft, so the form cannot
|
|
69
|
+
* compute them and this is the only place that can. They ride the existing
|
|
70
|
+
* response exactly as `partBreakdown` does.
|
|
71
|
+
*
|
|
72
|
+
* 🛑 `series` and `system` are REAL FIELDS, not invented from the tree.
|
|
73
|
+
* `BeyDocument.series` is "the retail series" (`Beyblade Burst QuadStrike`)
|
|
74
|
+
* and `BeyDocument.system` is "the hardware generation" (`4D System`,
|
|
75
|
+
* `Hybrid Wheel System`) — which is why the seeded body renders them as two
|
|
76
|
+
* separate rows. A leaf pairs with a bey through `BeyDocument.categoryId`,
|
|
77
|
+
* and `getBeysForCategory` is the inverse of exactly that.
|
|
78
|
+
*
|
|
79
|
+
* 🛑 Resolved against the LEAF, never an ancestor — a bey describes one
|
|
80
|
+
* model, so inheriting a line's would tell every listing on that line it is
|
|
81
|
+
* the model that happened to join to the line node.
|
|
82
|
+
*/
|
|
83
|
+
const TAXONOMY_SLOTS = ["category", "categoryPath", "series", "system"];
|
|
84
|
+
/** Slots that need the bey read. Checked before paying for it. */
|
|
85
|
+
const BEY_SLOTS = ["series", "system"];
|
|
86
|
+
/**
|
|
87
|
+
* `Spinning Tops › Beyblade X › Attack Types › Dran Sword`.
|
|
88
|
+
*
|
|
89
|
+
* A chevron rather than a slash: the body renders it inside
|
|
90
|
+
* `<li><strong>Category:</strong> …</li>` next to prose, and a slash there
|
|
91
|
+
* reads as a URL.
|
|
92
|
+
*/
|
|
93
|
+
const CATEGORY_PATH_SEPARATOR = " › ";
|
|
55
94
|
/**
|
|
56
95
|
* The variants a caller may ask for.
|
|
57
96
|
*
|
|
@@ -172,6 +211,10 @@ async function resolveInherited(parentIds, variant, seed) {
|
|
|
172
211
|
* that it came from the line.
|
|
173
212
|
*/
|
|
174
213
|
const byId = new Map(ancestors.map((a) => [a.id, a]));
|
|
214
|
+
// See `Inherited.ancestorNames` — before the loop, which may break early.
|
|
215
|
+
out.ancestorNames = nearestFirst
|
|
216
|
+
.map((pid) => byId.get(pid)?.name?.trim())
|
|
217
|
+
.filter((n) => Boolean(n));
|
|
175
218
|
for (const pid of nearestFirst) {
|
|
176
219
|
const d = byId.get(pid);
|
|
177
220
|
if (!d)
|
|
@@ -208,11 +251,12 @@ export async function GET(request, context) {
|
|
|
208
251
|
* referenced, so the per-field merge below cannot mutate the document the
|
|
209
252
|
* repository cached.
|
|
210
253
|
*/
|
|
211
|
-
const { template, inheritedFrom, inheritedFromName, defaults } = await resolveInherited(parentIds, variant, {
|
|
254
|
+
const { template, inheritedFrom, inheritedFromName, defaults, ancestorNames } = await resolveInherited(parentIds, variant, {
|
|
212
255
|
template: pickTemplate(leaf, variant),
|
|
213
256
|
inheritedFrom: null,
|
|
214
257
|
inheritedFromName: null,
|
|
215
258
|
defaults: { ...(leaf.productDefaults ?? {}) },
|
|
259
|
+
ancestorNames: [],
|
|
216
260
|
});
|
|
217
261
|
/*
|
|
218
262
|
* 🛑 `placeholders` is RE-DERIVED here and not trusted from the stored
|
|
@@ -256,6 +300,68 @@ export async function GET(request, context) {
|
|
|
256
300
|
else
|
|
257
301
|
body = stripSlotLine(body, PART_BREAKDOWN_SLOT);
|
|
258
302
|
}
|
|
303
|
+
/*
|
|
304
|
+
* ── The taxonomy slots ────────────────────────────────────────────────
|
|
305
|
+
*
|
|
306
|
+
* See TAXONOMY_SLOTS. `category` and `categoryPath` are free — the leaf
|
|
307
|
+
* and its ancestors are already in hand — so they are built
|
|
308
|
+
* unconditionally and the `placeholders` check below decides whether they
|
|
309
|
+
* are sent.
|
|
310
|
+
*
|
|
311
|
+
* The bey read is gated on the body actually asking, the same way the
|
|
312
|
+
* composition read is: nothing outside the Beyblade lines carries
|
|
313
|
+
* `{{series}}`/`{{system}}`, so a TCG set or a Hot Wheels casting pays
|
|
314
|
+
* nothing for a concept it does not have.
|
|
315
|
+
*/
|
|
316
|
+
if (template) {
|
|
317
|
+
const taxonomy = {
|
|
318
|
+
category: (leaf.name ?? "").trim(),
|
|
319
|
+
// Root-first: `ancestorNames` is nearest-first, so reverse it and put
|
|
320
|
+
// the leaf on the end. This is the order a breadcrumb reads in.
|
|
321
|
+
categoryPath: [...ancestorNames]
|
|
322
|
+
.reverse()
|
|
323
|
+
.concat((leaf.name ?? "").trim())
|
|
324
|
+
.filter(Boolean)
|
|
325
|
+
.join(CATEGORY_PATH_SEPARATOR),
|
|
326
|
+
};
|
|
327
|
+
if (BEY_SLOTS.some((s) => placeholders.includes(s))) {
|
|
328
|
+
const beys = await getBeysForCategory(id);
|
|
329
|
+
/*
|
|
330
|
+
* The first pairing only, matching `formatPartBreakdown`'s rule and
|
|
331
|
+
* for the same reason: 22 leaves pair with more than one bey (a
|
|
332
|
+
* remake shares a model's name), and concatenating two retail series
|
|
333
|
+
* would describe a release that does not exist.
|
|
334
|
+
*/
|
|
335
|
+
const bey = beys[0];
|
|
336
|
+
taxonomy.series = (bey?.series ?? "").trim();
|
|
337
|
+
taxonomy.system = (bey?.system ?? "").trim();
|
|
338
|
+
}
|
|
339
|
+
for (const slot of TAXONOMY_SLOTS) {
|
|
340
|
+
if (!placeholders.includes(slot))
|
|
341
|
+
continue;
|
|
342
|
+
const value = taxonomy[slot] ?? "";
|
|
343
|
+
if (value) {
|
|
344
|
+
slots[slot] = value;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
/*
|
|
348
|
+
* 🛑 ABSENCE REMOVES THE LINE — `partBreakdown`'s contract, applied
|
|
349
|
+
* here for the same reason: `interpolate` resolves an unfilled slot
|
|
350
|
+
* to an empty string, so `<li><strong>System:</strong> {{system}}</li>`
|
|
351
|
+
* would render an empty bullet that reads as broken data.
|
|
352
|
+
*
|
|
353
|
+
* 🛑 KNOWN CONSEQUENCE, stated rather than guarded against (as the
|
|
354
|
+
* strip helper's own header does): `{{series}}` appears both as a
|
|
355
|
+
* standalone row AND mid-sentence ("from the {{series}} line"), so a
|
|
356
|
+
* leaf with no paired bey loses that whole sentence instead of
|
|
357
|
+
* printing "from the line". Losing a sentence is the better of the
|
|
358
|
+
* two, and the seller sees the picker's note either way — but a
|
|
359
|
+
* template author adding an inline slot should know the line goes
|
|
360
|
+
* with it.
|
|
361
|
+
*/
|
|
362
|
+
body = stripSlotLine(body, slot);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
259
365
|
return NextResponse.json({
|
|
260
366
|
success: true,
|
|
261
367
|
data: {
|
|
@@ -177,7 +177,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
177
177
|
const ldFaq = brand?.faqs && brand.faqs.length > 0
|
|
178
178
|
? faqJsonLd(brand.faqs.map((f) => ({ question: f.question, answer: f.answer })))
|
|
179
179
|
: null;
|
|
180
|
-
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(
|
|
180
|
+
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(Stack, { gap: "xs", className: "min-w-0", children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.id && (_jsx(RecordOwnerQuickActions, { resource: "BRAND", recordId: brand.id, placement: "detail",
|
|
181
181
|
/* See CategoryDetailPageView — ghost ink is invisible on the cover hero. */
|
|
182
|
-
onMedia: hasCover, className: "mb-2", recordLabel: brand.name ?? slug })),
|
|
182
|
+
onMedia: hasCover, className: "mb-2", recordLabel: brand.name ?? slug })), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: "max-w-2xl mt-1", size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), brandContentBody && brandContentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: brandContentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, totals: totals, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
183
183
|
}
|
|
@@ -66,7 +66,13 @@ export function BreadcrumbTrail({ crumbs, category, rootLabel = "All Categories"
|
|
|
66
66
|
* missing and gave them no way to reach it.
|
|
67
67
|
*/
|
|
68
68
|
const leadCount = hasCollapsed ? split.lead.length : 0;
|
|
69
|
-
return (
|
|
69
|
+
return (
|
|
70
|
+
/*
|
|
71
|
+
* No marker class here — it lives on the `<Breadcrumb>` primitive this
|
|
72
|
+
* renders below, which is also what the scam registry's pages use
|
|
73
|
+
* directly. One owner; see `Breadcrumb.tsx`.
|
|
74
|
+
*/
|
|
75
|
+
_jsxs(Div, { className: className, children: [_jsxs(Row, { align: "center", gap: "xs", wrap: true, children: [_jsx(Breadcrumb, { items: visible.slice(0, leadCount), tone: tone }), hasCollapsed && (_jsxs(Details, { tone: "card", padding: "sm", rounded: "lg", children: [_jsx(Summary, { size: "xs", color: "muted", ariaLabel: `Show ${split.collapsed.length} hidden category levels`, children: `… ${split.collapsed.length} more` }), _jsx(Div, { padding: "t-xs", children: _jsx(Stack, { align: "start", gap: "xs", children: split.collapsed.map((c) => (_jsx(TextLink, { href: c.url ?? rootHref, size: "xs", children: c.name }, c.url ?? c.name))) }) })] })), _jsx(Breadcrumb, { items: hasCollapsed ? visible.slice(leadCount) : visible, tone: tone })] }), alt.mode === "inline" && (_jsxs(Span, { size: "xs", color: "muted", children: ["Also in", " ", alt.items.map((a, i) => (_jsxs(React.Fragment, { children: [i > 0 && " · ", _jsx(TextLink, { href: a.url ?? rootHref, size: "xs", children: a.name })] }, a.url ?? a.name)))] })), alt.mode === "dropdown" && (_jsxs(Details, { tone: "card", padding: "sm", rounded: "lg", children: [_jsx(Summary, { size: "xs", color: "muted", ariaLabel: "Other categories this listing is filed under", children: `Also in ${alt.items.length} more` }), _jsx(Div, { padding: "t-xs", children: _jsx(Stack, { align: "start", gap: "xs", children: alt.items.map((a) => (_jsx(TextLink, { href: a.url ?? rootHref, size: "xs", children: a.name }, a.url ?? a.name))) }) })] }))] }));
|
|
70
76
|
}
|
|
71
77
|
function fromCrumb(c) {
|
|
72
78
|
return c.url ? { label: c.name, href: c.url } : { label: c.name };
|
|
@@ -26,7 +26,7 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, onAddToCart,
|
|
|
26
26
|
? formatCurrency(bundle.bundlePrice, "INR")
|
|
27
27
|
: BUNDLE_COPY.detail.priceFallback;
|
|
28
28
|
const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
|
|
29
|
-
return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(
|
|
29
|
+
return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsx(RecordOwnerQuickActions, { resource: "BUNDLE", recordId: String(bundle.id), placement: "detail", recordLabel: String(bundle.name) }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: priceLabel }) }), discount && (
|
|
30
30
|
// Both the struck-through total and the "N% off" badge are
|
|
31
31
|
// derived from the bundle price, so they disclose it even
|
|
32
32
|
// though the badge carries no currency symbol.
|
|
@@ -598,13 +598,13 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
598
598
|
const ldFaq = category?.faqs && category.faqs.length > 0
|
|
599
599
|
? faqJsonLd(category.faqs.map((f) => ({ question: f.question, answer: f.answer })))
|
|
600
600
|
: null;
|
|
601
|
-
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on LetItRip` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), category?.id && (_jsx(RecordOwnerQuickActions, { resource: "CATEGORY", recordId: category.id, placement: "detail",
|
|
601
|
+
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on LetItRip` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.id && (_jsx(RecordOwnerQuickActions, { resource: "CATEGORY", recordId: category.id, placement: "detail",
|
|
602
602
|
/*
|
|
603
603
|
* Same flag the rest of this hero branches on. Without it the
|
|
604
604
|
* pencil renders `ghost` (zinc-600) on the dark cover and is
|
|
605
605
|
* invisible — see Root Cause #67 and `OwnerQuickActions.onMedia`.
|
|
606
606
|
*/
|
|
607
|
-
onMedia: hasCover, className: "mb-3", recordLabel: category.name ?? slug })),
|
|
607
|
+
onMedia: hasCover, className: "mb-3", recordLabel: category.name ?? slug })), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: "max-w-2xl mb-4", size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), showCountSplit && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "muted", size: "xs", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [ownHere.toLocaleString(), " here \u00B7 ", inSubcategories.toLocaleString(), " in subcategories"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), category?.hasbroName && (_jsx(Section, { padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Row, { gap: "md", align: "center", wrap: true, children: [category.hasbroCoverImage && (_jsx(MediaImage, { src: category.hasbroCoverImage, alt: `${category.hasbroName} logo`, size: "thumbnail", className: "h-12 w-auto flex-shrink-0" })), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", transform: "uppercase", weight: "medium", children: "Also known as" }), _jsx(Text, { weight: "semibold", children: category.hasbroName }), _jsxs(Text, { size: "xs", color: "muted", children: ["The same wave, named differently outside Japan \u2014 Takara-Tomy releases it as ", category.name, ", Hasbro as ", category.hasbroName, ". Both names find these listings."] })] })] }) }) })), contentBody && contentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: contentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), pairedBeys.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "sm", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: pairedBeys.length === 1 ? "Specifications" : "Releases" }), _jsx(Stack, { gap: "xs", className: "min-w-0", children: pairedBeys.map((bey) => (_jsxs(Row, { gap: "sm", align: "center", wrap: true, className: "min-w-0", children: [_jsx(TextLink, { href: String(ROUTES.PUBLIC.BEY_DETAIL(bey.slug)), children: bey.name }), [bey.system, bey.productCodes?.[0]]
|
|
608
608
|
.filter(Boolean)
|
|
609
609
|
.map((bit) => (_jsx(Text, { size: "sm", color: "muted", children: String(bit) }, String(bit))))] }, bey.id))) })] }) }) })), compositions.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "md", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: "What it is made of" }), _jsx(ModelCompositionPanel, { compositions: compositions, hrefForPart: (slot, tail) => String(ROUTES.PUBLIC.PART_DETAIL(slot, tail)) })] }) }) })), (() => {
|
|
610
610
|
if (category?.tier !== 0)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
3
4
|
import { Div, FormGroup } from "../../../ui/index.js";
|
|
4
5
|
import { Label, Checkbox, RichTextEditor, Text } from "../../../ui/index.js";
|
|
6
|
+
import { FieldLabel } from "../../../ui/forms/index.js";
|
|
5
7
|
import { ImageUpload, MediaImage, useMediaUpload } from "../../media/index.js";
|
|
6
8
|
import { FormField } from "../../../ui/index.js";
|
|
7
9
|
import { flattenCategories } from "../types/index.js";
|
|
@@ -25,6 +27,15 @@ const DEFAULT_LABELS = {
|
|
|
25
27
|
export function CategoryForm({ category, allCategories, onChange, isReadonly = false, labels = {}, stackClassName = "space-y-4", }) {
|
|
26
28
|
const { upload } = useMediaUpload();
|
|
27
29
|
const L = { ...DEFAULT_LABELS, ...labels };
|
|
30
|
+
/*
|
|
31
|
+
* Unique per instance via `useId`, not a literal. Two CategoryForms on one
|
|
32
|
+
* page would otherwise both emit the same label id, and `aria-labelledby`
|
|
33
|
+
* resolves the FIRST match — so the second editor would be named by the
|
|
34
|
+
* first form’s label. The same defect FieldInput’s own note records
|
|
35
|
+
* fixing for `htmlFor`.
|
|
36
|
+
*/
|
|
37
|
+
const reactId = useId();
|
|
38
|
+
const descriptionLabelId = `category-description-label-${reactId}`;
|
|
28
39
|
const update = (partial) => {
|
|
29
40
|
onChange({ ...category, ...partial });
|
|
30
41
|
};
|
|
@@ -35,7 +46,7 @@ export function CategoryForm({ category, allCategories, onChange, isReadonly = f
|
|
|
35
46
|
const handleNameChange = (value) => {
|
|
36
47
|
update({ name: value, slug: makeSlug(value, category.isBrand ?? false) });
|
|
37
48
|
};
|
|
38
|
-
return (_jsxs(Div, { className: stackClassName, children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "name", label: L.name, type: "text", value: category.name || "", onChange: handleNameChange, disabled: isReadonly }), _jsx(FormField, { name: "slug", label: L.slug, type: "text", value: category.slug || "", onChange: (value) => update({ slug: value }), disabled: isReadonly })] }), _jsxs(Div, { className: "appkit-form-field", children: [_jsx(
|
|
49
|
+
return (_jsxs(Div, { className: stackClassName, children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "name", label: L.name, type: "text", value: category.name || "", onChange: handleNameChange, disabled: isReadonly }), _jsx(FormField, { name: "slug", label: L.slug, type: "text", value: category.slug || "", onChange: (value) => update({ slug: value }), disabled: isReadonly })] }), _jsxs(Div, { className: "appkit-form-field", children: [_jsx(FieldLabel, { id: descriptionLabelId, children: L.description }), _jsx(RichTextEditor, { value: normalizeRichTextHtml(category.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, minHeightClassName: "min-h-[140px]", placeholder: "Enter category description", ariaLabelledBy: descriptionLabelId }), _jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: "Rich text is supported for category descriptions." })] }), !isReadonly && (_jsx(ImageUpload, { currentImage: category.display?.coverImage, onUpload: (file) => upload(file, "categories", true, {
|
|
39
50
|
type: "category-image",
|
|
40
51
|
name: category.name || "category",
|
|
41
52
|
}), onChange: (url) => update({ display: { ...category.display, coverImage: url } }), label: L.categoryImage, helperText: L.imageRecommended })), category.display?.coverImage && isReadonly && (_jsxs(Div, { children: [_jsx(Label, { className: "block mb-2", size: "sm", weight: "medium", children: L.categoryImage }), _jsx(Div, { className: `relative h-32 w-40 ${__O.hidden}`, rounded: "default", children: _jsx(MediaImage, { src: category.display.coverImage, alt: category.name || "", size: "card" }) })] })), _jsx(FormField, { name: "parentId", label: L.parentCategory, type: "select", value: category.parentId || "", onChange: (value) => update({ parentId: value || null }), disabled: isReadonly, options: [
|
|
@@ -70,12 +70,45 @@ function unwrap(res) {
|
|
|
70
70
|
const body = res?.data ?? res;
|
|
71
71
|
return body && typeof body === "object" && "template" in body ? body : null;
|
|
72
72
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
73
|
+
/**
|
|
74
|
+
* `camelCase` / `snake_case` slot name -> words a seller reads.
|
|
75
|
+
*
|
|
76
|
+
* `categoryPath` -> "category path", `partBreakdown` -> "part breakdown".
|
|
77
|
+
* Lower-cased throughout because the note reads these mid-sentence.
|
|
78
|
+
*/
|
|
79
|
+
function humaniseSlot(name) {
|
|
80
|
+
return name
|
|
81
|
+
.replace(/[_-]+/g, " ")
|
|
82
|
+
.replace(/([a-z\d])([A-Z])/g, "$1 $2")
|
|
83
|
+
.trim()
|
|
84
|
+
.toLowerCase();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* "2 details were left blank and are highlighted below: year, type"
|
|
88
|
+
*
|
|
89
|
+
* 🛑 NO `{{…}}` IN USER-FACING COPY. This printed the slot names back wrapped
|
|
90
|
+
* in braces — `2 of 9 slots could not be filled: {{year}}, {{type}}` — which
|
|
91
|
+
* is where the reported "raw {{slot}} syntax" came from. It was not a failed
|
|
92
|
+
* interpolation leaking through; the note RE-MANUFACTURED the template syntax
|
|
93
|
+
* to describe it, so the seller saw the same thing either way and no amount of
|
|
94
|
+
* fixing the templating would have removed it.
|
|
95
|
+
*
|
|
96
|
+
* "slots" went with the braces. It is the word this codebase uses for a
|
|
97
|
+
* placeholder; a seller filling in a listing has no reason to know it, and
|
|
98
|
+
* "could not be filled" reads as a system failure rather than as the three
|
|
99
|
+
* boxes they need to type in. The count stays — it is the actionable part —
|
|
100
|
+
* but it counts what is MISSING rather than reporting it as a fraction, since
|
|
101
|
+
* "2 of 9" invites the reader to wonder what the other seven were — and the
|
|
102
|
+
* denominator is why the `total` argument is gone.
|
|
103
|
+
*/
|
|
104
|
+
function unresolvedNote(unresolved) {
|
|
75
105
|
if (unresolved.length === 0)
|
|
76
106
|
return null;
|
|
77
|
-
const named = unresolved.map(
|
|
78
|
-
|
|
107
|
+
const named = unresolved.map(humaniseSlot).join(", ");
|
|
108
|
+
const count = unresolved.length;
|
|
109
|
+
return count === 1
|
|
110
|
+
? `One detail was left blank — add the ${named} before publishing.`
|
|
111
|
+
: `${count} details were left blank — add the ${named} before publishing.`;
|
|
79
112
|
}
|
|
80
113
|
/** "adds 2 specification rows · sets condition to Used" */
|
|
81
114
|
function describeDefaults(defaults) {
|
|
@@ -165,7 +198,7 @@ export function CategoryTemplatePicker({ categoryId, condition, context, isDescr
|
|
|
165
198
|
if (!data || !template || !filled) {
|
|
166
199
|
return (_jsx(Div, { className: PANEL_CLASS, children: _jsx(Text, { size: "xs", color: "muted", children: "No description template for this category yet \u2014 write your own, and an admin can add one so the next listing starts from it." }) }));
|
|
167
200
|
}
|
|
168
|
-
const note = unresolvedNote(filled.unresolved
|
|
201
|
+
const note = unresolvedNote(filled.unresolved);
|
|
169
202
|
const extras = describeDefaults(data.defaults);
|
|
170
203
|
const source = data.inheritedFromName
|
|
171
204
|
? `Inherited from ${data.inheritedFromName}`
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Market Price Index — the COMPUTED asking-price band for one leaf.
|
|
3
|
+
*
|
|
4
|
+
* The third collection in a deliberate trio, and the three are not
|
|
5
|
+
* interchangeable:
|
|
6
|
+
*
|
|
7
|
+
* `priceObservations` one dated sighting (raw, append-only)
|
|
8
|
+
* `marketPriceIndex` what asks cluster on (derived, this file)
|
|
9
|
+
* `priceIndex` what SOLD here (derived, our own orders)
|
|
10
|
+
*
|
|
11
|
+
* 🛑 The second and third are never blended, and the rule predates this file:
|
|
12
|
+
* *"They are rendered side by side and never averaged. The gap between them is
|
|
13
|
+
* the most useful thing we can tell a seller."* An ask is not a sale — 76–93%
|
|
14
|
+
* of crawled competitor listings are sold out without ever converting — so a
|
|
15
|
+
* blended figure would describe neither event and could not be unpicked later.
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 Why this is not written onto `categories`
|
|
18
|
+
*
|
|
19
|
+
* Same reason as its two siblings, and it is a measured one rather than a
|
|
20
|
+
* stylistic preference: `onCategoryWrite` is a `documentWritten` trigger on
|
|
21
|
+
* `categories`, and Root Cause #92 is a trigger that wrote back to the
|
|
22
|
+
* collection it watched producing **1,017,548 invocations in 24 hours**.
|
|
23
|
+
* `notifyRevalidate` also fires on every category write, so a nightly band
|
|
24
|
+
* write would drop each category page's ISR entry on a project that has
|
|
25
|
+
* already been paused once for ISR-write overage.
|
|
26
|
+
*
|
|
27
|
+
* ## 🛑 It does NOT replace the seeded band — it OUTRANKS it
|
|
28
|
+
*
|
|
29
|
+
* `CategoryPriceGuidance.market` stays exactly as seeded. A computed band wins
|
|
30
|
+
* where one exists, and where one does not the seeded generation band still
|
|
31
|
+
* shows, clearly labelled as the generation's. Overwriting the seed would mean
|
|
32
|
+
* a single bad import could erase the only evidence ~60 leaves have, with no
|
|
33
|
+
* way back — the same trap as `priceIndexRollup` being forbidden from touching
|
|
34
|
+
* `market`.
|
|
35
|
+
*
|
|
36
|
+
* @tag domain:price-index
|
|
37
|
+
* @tag layer:repository
|
|
38
|
+
* @tag pattern:repository
|
|
39
|
+
* @tag access:server-only
|
|
40
|
+
* @tag consumers:marketPriceRollup,price-guidance-resolve,price-index/market
|
|
41
|
+
* @tag sideEffects:writes marketPriceIndex
|
|
42
|
+
*/
|
|
43
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
44
|
+
import type { PriceObservationKind } from "./price-observation.repository";
|
|
45
|
+
/** One computed band. Mirrors `MarketStats` plus its provenance. */
|
|
46
|
+
export interface MarketPriceBand {
|
|
47
|
+
/**
|
|
48
|
+
* The price sightings cluster on — the headline.
|
|
49
|
+
*
|
|
50
|
+
* 🛑 Optional, and the renderer must branch. Absent means no bucket held two
|
|
51
|
+
* or more sightings, i.e. every observed price was different, so there is no
|
|
52
|
+
* "most common" to report and `median` is shown under a different label. A
|
|
53
|
+
* mode synthesised from a single sighting would be that sighting wearing a
|
|
54
|
+
* word that implies consensus.
|
|
55
|
+
*/
|
|
56
|
+
mode?: number;
|
|
57
|
+
modeCount?: number;
|
|
58
|
+
median: number;
|
|
59
|
+
low: number;
|
|
60
|
+
high: number;
|
|
61
|
+
sampleSize: number;
|
|
62
|
+
/**
|
|
63
|
+
* 🛑 FALSE means low/high/mode describe ALL TIME, not the last six months —
|
|
64
|
+
* the window held too little to stand alone. The renderer must relabel. A
|
|
65
|
+
* figure drawn from a two-year-old crawl is indistinguishable from a current
|
|
66
|
+
* one once printed, which is why this travels with the band rather than being
|
|
67
|
+
* inferred from `computedAt`.
|
|
68
|
+
*/
|
|
69
|
+
basedOnRecent: boolean;
|
|
70
|
+
windowDays: number;
|
|
71
|
+
/** Newest sighting in the set the band came from. Not the rollup time. */
|
|
72
|
+
lastSeenAt: Date;
|
|
73
|
+
}
|
|
74
|
+
export interface MarketPriceIndexDocument {
|
|
75
|
+
/** The leaf id — model, era, part group, whatever was observed. */
|
|
76
|
+
id: string;
|
|
77
|
+
currency: "INR";
|
|
78
|
+
/**
|
|
79
|
+
* Bands kept APART by event kind, never merged.
|
|
80
|
+
*
|
|
81
|
+
* Keyed by `PriceObservationKind`. `ask` is what shops list at; `sale` is
|
|
82
|
+
* what we have evidence changed hands at elsewhere. Most leaves will only
|
|
83
|
+
* ever have `ask`, and that is an honest state rather than a gap.
|
|
84
|
+
*/
|
|
85
|
+
byKind: Partial<Record<PriceObservationKind, MarketPriceBand>>;
|
|
86
|
+
computedAt: Date;
|
|
87
|
+
}
|
|
88
|
+
export declare const MARKET_PRICE_INDEX_COLLECTION = "marketPriceIndex";
|
|
89
|
+
export declare class MarketPriceIndexRepository extends BaseRepository<MarketPriceIndexDocument> {
|
|
90
|
+
constructor();
|
|
91
|
+
/**
|
|
92
|
+
* Bands for several leaves in ONE round trip.
|
|
93
|
+
*
|
|
94
|
+
* 🛑 `db.getAll`, not N `findById` calls — the same decision, for the same
|
|
95
|
+
* reason, as `PriceIndexRepository.getManyForLeaves`: the tool resolves up to
|
|
96
|
+
* `PRICE_INDEX_SEARCH_LIMIT` leaves per lookup and N sequential reads is N
|
|
97
|
+
* round trips against Rule #6's ~3-per-request budget.
|
|
98
|
+
*
|
|
99
|
+
* A missing document is OMITTED, never returned as an empty band — "no
|
|
100
|
+
* computed band here" and "this leaf does not exist" are different answers,
|
|
101
|
+
* and a zeroed band renders as a confident ₹0.
|
|
102
|
+
*/
|
|
103
|
+
getManyForLeaves(leafIds: readonly string[]): Promise<Map<string, MarketPriceIndexDocument>>;
|
|
104
|
+
/**
|
|
105
|
+
* Write one leaf's bands.
|
|
106
|
+
*
|
|
107
|
+
* `byKind` is replaced WHOLESALE rather than merged: `update` merges, so a
|
|
108
|
+
* kind whose observations were all retracted would keep its stale band
|
|
109
|
+
* forever — the same reason `PriceIndexRepository.setForLeaf` passes the full
|
|
110
|
+
* map rather than a patch.
|
|
111
|
+
*/
|
|
112
|
+
setForLeaf(leafId: string, byKind: MarketPriceIndexDocument["byKind"]): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Drop a leaf's bands entirely.
|
|
115
|
+
*
|
|
116
|
+
* Reachable state, not defensive coding: a leaf whose observations are all
|
|
117
|
+
* retracted, or all fall below `MIN_BAND_OBSERVATIONS` after a retraction,
|
|
118
|
+
* must stop publishing a band. Leaving the last computed one in place would
|
|
119
|
+
* publish a figure derived from evidence that no longer exists.
|
|
120
|
+
*/
|
|
121
|
+
clearForLeaf(leafId: string): Promise<void>;
|
|
122
|
+
}
|
|
123
|
+
export declare const marketPriceIndexRepository: MarketPriceIndexRepository;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Market Price Index — the COMPUTED asking-price band for one leaf.
|
|
3
|
+
*
|
|
4
|
+
* The third collection in a deliberate trio, and the three are not
|
|
5
|
+
* interchangeable:
|
|
6
|
+
*
|
|
7
|
+
* `priceObservations` one dated sighting (raw, append-only)
|
|
8
|
+
* `marketPriceIndex` what asks cluster on (derived, this file)
|
|
9
|
+
* `priceIndex` what SOLD here (derived, our own orders)
|
|
10
|
+
*
|
|
11
|
+
* 🛑 The second and third are never blended, and the rule predates this file:
|
|
12
|
+
* *"They are rendered side by side and never averaged. The gap between them is
|
|
13
|
+
* the most useful thing we can tell a seller."* An ask is not a sale — 76–93%
|
|
14
|
+
* of crawled competitor listings are sold out without ever converting — so a
|
|
15
|
+
* blended figure would describe neither event and could not be unpicked later.
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 Why this is not written onto `categories`
|
|
18
|
+
*
|
|
19
|
+
* Same reason as its two siblings, and it is a measured one rather than a
|
|
20
|
+
* stylistic preference: `onCategoryWrite` is a `documentWritten` trigger on
|
|
21
|
+
* `categories`, and Root Cause #92 is a trigger that wrote back to the
|
|
22
|
+
* collection it watched producing **1,017,548 invocations in 24 hours**.
|
|
23
|
+
* `notifyRevalidate` also fires on every category write, so a nightly band
|
|
24
|
+
* write would drop each category page's ISR entry on a project that has
|
|
25
|
+
* already been paused once for ISR-write overage.
|
|
26
|
+
*
|
|
27
|
+
* ## 🛑 It does NOT replace the seeded band — it OUTRANKS it
|
|
28
|
+
*
|
|
29
|
+
* `CategoryPriceGuidance.market` stays exactly as seeded. A computed band wins
|
|
30
|
+
* where one exists, and where one does not the seeded generation band still
|
|
31
|
+
* shows, clearly labelled as the generation's. Overwriting the seed would mean
|
|
32
|
+
* a single bad import could erase the only evidence ~60 leaves have, with no
|
|
33
|
+
* way back — the same trap as `priceIndexRollup` being forbidden from touching
|
|
34
|
+
* `market`.
|
|
35
|
+
*
|
|
36
|
+
* @tag domain:price-index
|
|
37
|
+
* @tag layer:repository
|
|
38
|
+
* @tag pattern:repository
|
|
39
|
+
* @tag access:server-only
|
|
40
|
+
* @tag consumers:marketPriceRollup,price-guidance-resolve,price-index/market
|
|
41
|
+
* @tag sideEffects:writes marketPriceIndex
|
|
42
|
+
*/
|
|
43
|
+
import { BaseRepository } from "../../../providers/db-firebase/index.js";
|
|
44
|
+
export const MARKET_PRICE_INDEX_COLLECTION = "marketPriceIndex";
|
|
45
|
+
export class MarketPriceIndexRepository extends BaseRepository {
|
|
46
|
+
constructor() {
|
|
47
|
+
super(MARKET_PRICE_INDEX_COLLECTION);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Bands for several leaves in ONE round trip.
|
|
51
|
+
*
|
|
52
|
+
* 🛑 `db.getAll`, not N `findById` calls — the same decision, for the same
|
|
53
|
+
* reason, as `PriceIndexRepository.getManyForLeaves`: the tool resolves up to
|
|
54
|
+
* `PRICE_INDEX_SEARCH_LIMIT` leaves per lookup and N sequential reads is N
|
|
55
|
+
* round trips against Rule #6's ~3-per-request budget.
|
|
56
|
+
*
|
|
57
|
+
* A missing document is OMITTED, never returned as an empty band — "no
|
|
58
|
+
* computed band here" and "this leaf does not exist" are different answers,
|
|
59
|
+
* and a zeroed band renders as a confident ₹0.
|
|
60
|
+
*/
|
|
61
|
+
async getManyForLeaves(leafIds) {
|
|
62
|
+
const ids = [...new Set(leafIds.filter(Boolean))];
|
|
63
|
+
const out = new Map();
|
|
64
|
+
if (ids.length === 0)
|
|
65
|
+
return out;
|
|
66
|
+
const refs = ids.map((id) => this.db.collection(MARKET_PRICE_INDEX_COLLECTION).doc(id));
|
|
67
|
+
const snaps = await this.db.getAll(...refs);
|
|
68
|
+
for (const snap of snaps) {
|
|
69
|
+
if (!snap.exists)
|
|
70
|
+
continue;
|
|
71
|
+
out.set(snap.id, { ...snap.data(), id: snap.id });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Write one leaf's bands.
|
|
77
|
+
*
|
|
78
|
+
* `byKind` is replaced WHOLESALE rather than merged: `update` merges, so a
|
|
79
|
+
* kind whose observations were all retracted would keep its stale band
|
|
80
|
+
* forever — the same reason `PriceIndexRepository.setForLeaf` passes the full
|
|
81
|
+
* map rather than a patch.
|
|
82
|
+
*/
|
|
83
|
+
async setForLeaf(leafId, byKind) {
|
|
84
|
+
const payload = { currency: "INR", byKind, computedAt: new Date() };
|
|
85
|
+
const existing = await this.findById(leafId);
|
|
86
|
+
if (existing)
|
|
87
|
+
await this.update(leafId, payload);
|
|
88
|
+
else
|
|
89
|
+
await this.createWithId(leafId, payload);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Drop a leaf's bands entirely.
|
|
93
|
+
*
|
|
94
|
+
* Reachable state, not defensive coding: a leaf whose observations are all
|
|
95
|
+
* retracted, or all fall below `MIN_BAND_OBSERVATIONS` after a retraction,
|
|
96
|
+
* must stop publishing a band. Leaving the last computed one in place would
|
|
97
|
+
* publish a figure derived from evidence that no longer exists.
|
|
98
|
+
*/
|
|
99
|
+
async clearForLeaf(leafId) {
|
|
100
|
+
const existing = await this.findById(leafId);
|
|
101
|
+
if (existing)
|
|
102
|
+
await this.delete(leafId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export const marketPriceIndexRepository = new MarketPriceIndexRepository();
|