@mohasinac/appkit 4.62.0 → 4.63.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 +8 -29
- package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
- package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
- package/dist/_internal/server/features/ads/booking.d.ts +118 -0
- package/dist/_internal/server/features/ads/booking.js +326 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +129 -9
- package/dist/_internal/server/features/checkout/actions.js +850 -79
- package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
- package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
- package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
- package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
- package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
- package/dist/_internal/server/features/dashboard/attention.js +183 -0
- package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
- package/dist/_internal/server/features/dashboard/data.js +82 -0
- package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
- package/dist/_internal/server/features/inventory/reservations.js +273 -0
- package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
- package/dist/_internal/server/features/invoicing/allocate.js +162 -0
- package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
- package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
- package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
- package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
- package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
- package/dist/_internal/server/features/invoicing/issue.js +202 -0
- package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
- package/dist/_internal/server/features/offers/destinations.js +65 -0
- package/dist/_internal/server/features/orders/actions.d.ts +8 -1
- package/dist/_internal/server/features/orders/actions.js +81 -8
- package/dist/_internal/server/features/orders/adapters.js +54 -0
- package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
- package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
- package/dist/_internal/server/features/orders/balance.d.ts +89 -0
- package/dist/_internal/server/features/orders/balance.js +256 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
- package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
- package/dist/_internal/server/features/points/award.js +46 -4
- package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
- package/dist/_internal/server/features/pricing/actions.js +144 -0
- package/dist/_internal/server/features/refunds/actions.js +26 -0
- package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
- package/dist/_internal/server/features/shipping/book-split.js +130 -0
- package/dist/_internal/server/features/shipping/book.d.ts +61 -0
- package/dist/_internal/server/features/shipping/book.js +176 -0
- package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
- package/dist/_internal/server/features/shipping/courier-block.js +264 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
- package/dist/_internal/server/features/site-settings/adapters.js +29 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
- package/dist/_internal/server/features/stores/adapters.js +65 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +95 -2
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +11 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
- package/dist/_internal/server/jobs/core/index.d.ts +2 -1
- package/dist/_internal/server/jobs/core/index.js +2 -1
- package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
- package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
- package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +65 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
- package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
- package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
- package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
- package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +25 -2
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +296 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
- package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
- package/dist/_internal/shared/checkout/order-math.d.ts +174 -0
- package/dist/_internal/shared/checkout/order-math.js +159 -0
- package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
- package/dist/_internal/shared/checkout/refundable-base.js +13 -1
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
- package/dist/_internal/shared/checkout/split.d.ts +75 -0
- package/dist/_internal/shared/checkout/split.js +120 -0
- package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
- package/dist/_internal/shared/content/disclaimers.js +217 -0
- package/dist/_internal/shared/content/published-values.d.ts +93 -0
- package/dist/_internal/shared/content/published-values.js +206 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +139 -0
- package/dist/_internal/shared/features/admin/integrations.js +263 -0
- package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
- package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
- package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
- package/dist/_internal/shared/features/emi/choice.js +69 -0
- package/dist/_internal/shared/features/emi/group.d.ts +84 -0
- package/dist/_internal/shared/features/emi/group.js +126 -0
- package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
- package/dist/_internal/shared/features/ledger/posting.js +165 -0
- package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
- package/dist/_internal/shared/features/ledger/reports.js +127 -0
- package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
- package/dist/_internal/shared/features/ledger/rules.js +221 -0
- package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
- package/dist/_internal/shared/features/offers/coordination.js +80 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
- package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
- package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
- package/dist/_internal/shared/features/points/accepts.d.ts +29 -0
- package/dist/_internal/shared/features/points/accepts.js +49 -0
- package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
- package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
- package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
- package/dist/_internal/shared/features/shipping/parcel.js +139 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
- package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
- package/dist/_internal/shared/features/site-settings/fees.js +12 -8
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/fees/calculator.d.ts +33 -1
- package/dist/_internal/shared/fees/calculator.js +32 -2
- package/dist/_internal/shared/fees/explain.d.ts +81 -0
- package/dist/_internal/shared/fees/explain.js +101 -0
- package/dist/_internal/shared/history/index.d.ts +1 -1
- package/dist/_internal/shared/history/index.js +1 -1
- package/dist/_internal/shared/history/types.d.ts +7 -1
- package/dist/_internal/shared/history/types.js +25 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.js +43 -0
- package/dist/constants/api-endpoints.d.ts +114 -0
- package/dist/constants/api-endpoints.js +55 -0
- package/dist/constants/field-names.d.ts +42 -0
- package/dist/constants/field-names.js +52 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/shipping.d.ts +45 -0
- package/dist/contracts/shipping.js +15 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
- package/dist/features/account/components/UserOrdersView.js +47 -35
- package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
- package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
- package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
- package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
- package/dist/features/admin/components/AdminInvoicingView.js +65 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +33 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +81 -3
- package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
- package/dist/features/admin/components/AdminOrdersView.js +86 -5
- package/dist/features/admin/components/OwnerQuickActions.js +24 -2
- package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
- package/dist/features/admin/constants/filter-tabs.js +6 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
- package/dist/features/admin/hooks/useAdminListingData.js +16 -0
- package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
- package/dist/features/admin/schemas/firestore.d.ts +62 -0
- package/dist/features/admin/schemas/firestore.js +5 -0
- package/dist/features/ads/schemas/booking-write.d.ts +120 -0
- package/dist/features/ads/schemas/booking-write.js +75 -0
- package/dist/features/ads/schemas/creative-form.d.ts +18 -0
- package/dist/features/ads/schemas/creative-form.js +51 -0
- package/dist/features/ads/schemas/firestore.d.ts +140 -0
- package/dist/features/ads/schemas/firestore.js +109 -0
- package/dist/features/auctions/repository/bid.repository.js +3 -1
- package/dist/features/audit-log/schemas/firestore.d.ts +140 -0
- package/dist/features/audit-log/schemas/firestore.js +89 -0
- package/dist/features/auth/actions/profile-actions.d.ts +69 -1
- package/dist/features/auth/actions/profile-actions.js +62 -1
- package/dist/features/auth/permissions/constants.js +13 -0
- package/dist/features/auth/schemas/firestore.d.ts +16 -2
- package/dist/features/auth/schemas/firestore.js +2 -1
- package/dist/features/blog/actions/blog-actions.d.ts +6 -0
- package/dist/features/blog/actions/blog-actions.js +17 -2
- package/dist/features/blog/api/route.js +4 -1
- package/dist/features/blog/columns/index.js +5 -0
- package/dist/features/blog/components/BlogIndexPageView.js +2 -1
- package/dist/features/blog/repository/blog.repository.d.ts +4 -0
- package/dist/features/blog/repository/blog.repository.js +5 -0
- package/dist/features/blog/schemas/blog-form.d.ts +3 -0
- package/dist/features/blog/schemas/blog-form.js +1 -0
- package/dist/features/blog/schemas/firestore.d.ts +38 -1
- package/dist/features/blog/schemas/firestore.js +23 -0
- package/dist/features/blog/schemas/index.d.ts +6 -0
- package/dist/features/blog/schemas/index.js +2 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +25 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +29 -6
- package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
- package/dist/features/cart/components/PointsTenderPicker.js +15 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
- package/dist/features/cart/components/index.d.ts +2 -0
- package/dist/features/cart/components/index.js +1 -0
- package/dist/features/cart/repository/cart.repository.d.ts +33 -0
- package/dist/features/cart/repository/cart.repository.js +47 -0
- package/dist/features/cart/schemas/firestore.d.ts +116 -0
- package/dist/features/contact/email.d.ts +27 -10
- package/dist/features/contact/email.js +20 -3
- package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
- package/dist/features/dashboard/components/DashboardHubView.js +56 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +62 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +108 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +62 -0
- package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
- package/dist/features/faq/components/FAQAccordion.js +29 -4
- package/dist/features/faq/components/FAQPageContent.js +2 -2
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/homepage/schemas/index.d.ts +8 -8
- package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
- package/dist/features/inventory/schemas/stock-reservation.js +103 -0
- package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
- package/dist/features/invoicing/schemas/firestore.js +45 -0
- package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
- package/dist/features/invoicing/utils/financial-year.js +53 -0
- package/dist/features/jobs/actions/enqueue-job.js +18 -1
- package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
- package/dist/features/jobs/repository/jobs.repository.js +84 -1
- package/dist/features/jobs/schemas/firestore.d.ts +80 -0
- package/dist/features/jobs/schemas/firestore.js +37 -0
- package/dist/features/layout/AppLayoutShell.d.ts +9 -1
- package/dist/features/layout/AppLayoutShell.js +12 -4
- package/dist/features/layout/BottomActions.js +2 -1
- package/dist/features/layout/BottomNavbar.d.ts +10 -2
- package/dist/features/layout/SideActionDock.js +52 -2
- package/dist/features/layout/TitleBarLayout.js +13 -4
- package/dist/features/layout/TitleBarLayout.style.css +59 -0
- package/dist/features/layout/useCtaReveal.d.ts +16 -1
- package/dist/features/layout/useCtaReveal.js +17 -26
- package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
- package/dist/features/ledger/repository/journal.repository.js +312 -0
- package/dist/features/ledger/schemas/expenses.d.ts +87 -0
- package/dist/features/ledger/schemas/expenses.js +27 -0
- package/dist/features/ledger/schemas/firestore.d.ts +183 -0
- package/dist/features/ledger/schemas/firestore.js +69 -0
- package/dist/features/lottery/schemas/zod.d.ts +6 -6
- package/dist/features/media/MediaImage.js +22 -1
- package/dist/features/media/upload/ImageUpload.js +1 -1
- package/dist/features/obligations/constants/policies.js +24 -1
- package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
- package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
- package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
- package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
- package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
- package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +119 -0
- package/dist/features/orders/components/RefundRequestView.js +19 -1
- package/dist/features/orders/components/index.d.ts +4 -0
- package/dist/features/orders/components/index.js +2 -0
- package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
- package/dist/features/orders/constants/order-lane-tabs.js +88 -0
- package/dist/features/orders/repository/orders.repository.d.ts +85 -1
- package/dist/features/orders/repository/orders.repository.js +205 -6
- package/dist/features/orders/schemas/balance.d.ts +40 -0
- package/dist/features/orders/schemas/balance.js +47 -0
- package/dist/features/orders/schemas/firestore.d.ts +398 -1
- package/dist/features/orders/schemas/firestore.js +43 -0
- package/dist/features/orders/schemas/index.d.ts +56 -8
- package/dist/features/orders/schemas/index.js +12 -0
- package/dist/features/orders/types/index.d.ts +47 -0
- package/dist/features/orders/utils/courier-block.d.ts +25 -0
- package/dist/features/orders/utils/courier-block.js +23 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
- package/dist/features/payment-cases/schemas/firestore.js +1 -0
- package/dist/features/payments/repository/payout.repository.js +3 -2
- package/dist/features/payments/schemas/firestore.d.ts +92 -1
- package/dist/features/payments/schemas/firestore.js +23 -0
- package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
- package/dist/features/pricing/components/AdminPricingView.js +157 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
- package/dist/features/products/components/ProductDetailPageView.js +2 -2
- package/dist/features/products/schemas/firestore.d.ts +26 -1
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
- package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
- package/dist/features/promotions/actions/coupon-stacking.js +21 -2
- package/dist/features/promotions/components/CouponCard.js +13 -0
- package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
- package/dist/features/promotions/constants/coupon-help.js +9 -1
- package/dist/features/promotions/schemas/firestore.d.ts +24 -0
- package/dist/features/promotions/schemas/firestore.js +4 -0
- package/dist/features/promotions/types/index.d.ts +19 -1
- package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
- package/dist/features/reviews/repository/reviews.repository.js +16 -0
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/scams/schemas/firestore.d.ts +2 -1
- package/dist/features/scams/schemas/firestore.js +2 -1
- package/dist/features/search/action-index/tab-entries.js +1 -1
- package/dist/features/seller/actions/offer-actions.js +56 -0
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +37 -3
- package/dist/features/seller/components/DispatchForm.d.ts +11 -0
- package/dist/features/seller/components/DispatchForm.js +190 -0
- package/dist/features/seller/components/SellerOrdersView.js +221 -27
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/hooks/useSellerListingData.js +25 -0
- package/dist/features/seller/repository/offer.repository.js +4 -3
- package/dist/features/seller/schemas/firestore.d.ts +23 -2
- package/dist/features/seller/schemas/firestore.js +3 -2
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +61 -4
- package/dist/features/seller/schemas/order-forms.js +41 -8
- package/dist/features/shipments/schemas/firestore.d.ts +25 -0
- package/dist/features/shipments/schemas/validation.d.ts +4 -4
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +82 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +145 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +80 -0
- package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
- package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
- package/dist/features/stores/repository/store.repository.d.ts +18 -0
- package/dist/features/stores/repository/store.repository.js +27 -1
- package/dist/features/stores/schemas/firestore.d.ts +112 -0
- package/dist/features/stores/schemas/firestore.js +20 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/stores/types/index.d.ts +19 -0
- package/dist/features/support/repository/support.repository.js +72 -6
- package/dist/features/support/schemas/firestore.d.ts +17 -0
- package/dist/features/support/schemas/index.d.ts +8 -8
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +33 -1
- package/dist/next/routing/route-map.d.ts +112 -0
- package/dist/next/routing/route-map.js +63 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
- package/dist/providers/shipping-shiprocket/index.js +84 -1
- package/dist/react/contexts/SessionContext.d.ts +17 -1
- package/dist/schemas/registry.d.ts +69 -34
- package/dist/schemas/sieve.d.ts +2 -2
- package/dist/security/pii-schemas.d.ts +1 -1
- package/dist/security/pii-schemas.js +45 -0
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +8 -1
- package/dist/server.d.ts +36 -1
- package/dist/server.js +183 -1
- package/dist/styles.css +412 -80
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +1 -0
- package/dist/tokens/themes/default-light.js +1 -0
- package/dist/tokens/themes/required.d.ts +1 -1
- package/dist/tokens/themes/required.js +1 -0
- package/dist/tokens/tokens.css +15 -0
- package/dist/ui/components/Button.d.ts +10 -1
- package/dist/ui/components/Button.js +25 -3
- package/dist/ui/components/Chart.d.ts +63 -0
- package/dist/ui/components/Chart.js +169 -0
- package/dist/ui/components/Chart.style.css +214 -0
- package/dist/ui/components/Disclaimer.d.ts +19 -0
- package/dist/ui/components/Disclaimer.js +83 -0
- package/dist/ui/components/OverflowRow.js +54 -2
- package/dist/ui/components/OverflowRow.style.css +38 -0
- package/dist/ui/components/RichTextEditor.js +99 -8
- package/dist/ui/components/chart-spec.d.ts +68 -0
- package/dist/ui/components/chart-spec.js +140 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/surface-tokens.d.ts +1 -1
- package/dist/ui/components/surface-tokens.js +9 -0
- package/dist/ui/components/tabs-overflow.js +64 -11
- package/dist/ui/components/toolbar-collapse.d.ts +13 -0
- package/dist/ui/components/toolbar-collapse.js +10 -1
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.js +9 -0
- package/dist/ui/rich-text/RichText.d.ts +10 -1
- package/dist/ui/rich-text/RichText.js +33 -2
- package/dist/ui/rich-text/RichText.style.css +83 -79
- package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
- package/dist/ui/rich-text/RichTextRenderer.js +34 -2
- package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
- package/dist/ui/rich-text/chart-blocks.js +87 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +1834 -1822
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { Badge, Button, Heading, Row, Stack, Text } from "../../../ui/index.js";
|
|
5
|
+
import { Modal } from "../../../ui/components/Modal.js";
|
|
6
|
+
import { useToast } from "../../../ui/components/Toast.js";
|
|
7
|
+
import { Form } from "../../../ui/components/Form.js";
|
|
8
|
+
import { FieldInput } from "../../../ui/forms/FieldInput.js";
|
|
9
|
+
import { FieldTextarea } from "../../../ui/forms/FieldTextarea.js";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
|
|
11
|
+
import { apiClient } from "../../../http/ApiClient.js";
|
|
12
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
13
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
14
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
15
|
+
import { RATE_TIER_ORDER, } from "../../../_internal/shared/features/pricing/rate-registry.js";
|
|
16
|
+
const TIER_LABEL = {
|
|
17
|
+
statutory: "Statutory",
|
|
18
|
+
policy: "Policy ceiling",
|
|
19
|
+
operational: "Operational",
|
|
20
|
+
};
|
|
21
|
+
const TIER_BLURB = {
|
|
22
|
+
statutory: "Set by law, not by us. Sourced per HSN code in Tax codes — they deliberately do not appear below.",
|
|
23
|
+
policy: "A ceiling the owner decides. Administrator only, and a reason is required.",
|
|
24
|
+
operational: "Tuned, audited and reversible by back-office staff.",
|
|
25
|
+
};
|
|
26
|
+
const TIER_TONE = {
|
|
27
|
+
statutory: "warning",
|
|
28
|
+
policy: "info",
|
|
29
|
+
operational: "default",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* 🛑 Format by the descriptor's UNIT, never by guessing from the value.
|
|
33
|
+
*
|
|
34
|
+
* `2` is 2% as a commission, ₹2 as a floor and 2 days as a hold, and the three
|
|
35
|
+
* are side by side in one table. A bare number would make the COD ceiling and
|
|
36
|
+
* the COD percentage read as the same kind of thing.
|
|
37
|
+
*/
|
|
38
|
+
function formatRate(value, unit) {
|
|
39
|
+
if (value === null)
|
|
40
|
+
return "—";
|
|
41
|
+
switch (unit) {
|
|
42
|
+
case "percent":
|
|
43
|
+
return `${value}%`;
|
|
44
|
+
case "rupees":
|
|
45
|
+
return `₹${value.toLocaleString("en-IN")}`;
|
|
46
|
+
case "days":
|
|
47
|
+
return value === 1 ? "1 day" : `${value} days`;
|
|
48
|
+
case "minutes":
|
|
49
|
+
return value === 1 ? "1 minute" : `${value} minutes`;
|
|
50
|
+
case "count":
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function formatWhen(iso) {
|
|
55
|
+
const d = new Date(iso);
|
|
56
|
+
return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString("en-IN", { dateStyle: "medium" });
|
|
57
|
+
}
|
|
58
|
+
export function AdminPricingView({ endpoint }) {
|
|
59
|
+
const { showToast } = useToast();
|
|
60
|
+
const [rates, setRates] = useState(null);
|
|
61
|
+
const [loadError, setLoadError] = useState(null);
|
|
62
|
+
const [editing, setEditing] = useState(null);
|
|
63
|
+
const [draftValue, setDraftValue] = useState("");
|
|
64
|
+
const [draftReason, setDraftReason] = useState("");
|
|
65
|
+
const [saving, setSaving] = useState(false);
|
|
66
|
+
const load = useCallback(async () => {
|
|
67
|
+
setLoadError(null);
|
|
68
|
+
try {
|
|
69
|
+
const payload = await apiClient.get(endpoint);
|
|
70
|
+
setRates(payload?.rates ?? []);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
/*
|
|
74
|
+
* Surfaced, never swallowed into an empty table. An empty rate list and
|
|
75
|
+
* a failed read look identical, and the first reads as "we charge
|
|
76
|
+
* nothing" (Root Cause #59/#72 — undefined must never render as zero).
|
|
77
|
+
*
|
|
78
|
+
* 🛑 A FIXED string, never `normalized.message`. The server's own text
|
|
79
|
+
* is how a Node `MODULE_NOT_FOUND` with a require stack ended up inside
|
|
80
|
+
* "Place your bid" (Root Cause #86) — and `mapToHttpError` scrubs 5xx
|
|
81
|
+
* messages in production anyway, so the raw value is either useless or
|
|
82
|
+
* dangerous. `normalizeError` still runs: it is what keeps `unknown`
|
|
83
|
+
* out of the catch body.
|
|
84
|
+
*/
|
|
85
|
+
void normalizeError(err);
|
|
86
|
+
setLoadError("Could not load the rate table. Try again.");
|
|
87
|
+
setRates([]);
|
|
88
|
+
}
|
|
89
|
+
}, [endpoint]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
void load();
|
|
92
|
+
}, [load]);
|
|
93
|
+
const byTier = useMemo(() => {
|
|
94
|
+
const groups = new Map();
|
|
95
|
+
for (const tier of RATE_TIER_ORDER)
|
|
96
|
+
groups.set(tier, []);
|
|
97
|
+
for (const rate of rates ?? [])
|
|
98
|
+
groups.get(rate.tier)?.push(rate);
|
|
99
|
+
return groups;
|
|
100
|
+
}, [rates]);
|
|
101
|
+
const openEditor = useCallback((rate) => {
|
|
102
|
+
setEditing(rate);
|
|
103
|
+
setDraftValue(rate.value === null ? "" : String(rate.value));
|
|
104
|
+
setDraftReason("");
|
|
105
|
+
}, []);
|
|
106
|
+
const submit = useCallback(async (setFieldError) => {
|
|
107
|
+
if (!editing)
|
|
108
|
+
return;
|
|
109
|
+
const parsed = Number(draftValue);
|
|
110
|
+
if (!Number.isFinite(parsed)) {
|
|
111
|
+
setFieldError("newValue", "Enter a number.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setSaving(true);
|
|
115
|
+
try {
|
|
116
|
+
await apiClient.post(endpoint, {
|
|
117
|
+
path: editing.path,
|
|
118
|
+
newValue: parsed,
|
|
119
|
+
...(draftReason.trim() ? { reason: draftReason.trim() } : {}),
|
|
120
|
+
});
|
|
121
|
+
showToast(`${editing.label} updated.`, "success");
|
|
122
|
+
setEditing(null);
|
|
123
|
+
await load();
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
void normalizeError(err);
|
|
127
|
+
/*
|
|
128
|
+
* 🛑 `toUserMessage`, never the server's own message — and no
|
|
129
|
+
* `?? normalized.message` fallback either. That fallback IS the bug:
|
|
130
|
+
* `toUserMessage` already terminates at `errors.codes.UNKNOWN`, so
|
|
131
|
+
* adding a raw-text branch for the unmapped case reintroduces exactly
|
|
132
|
+
* what Root Cause #86 describes — a require stack rendered inside a
|
|
133
|
+
* modal. The first draft of this file had it.
|
|
134
|
+
*/
|
|
135
|
+
const code = err?.code;
|
|
136
|
+
setFieldError("newValue", toUserMessage(code ?? "UNKNOWN"));
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
setSaving(false);
|
|
140
|
+
}
|
|
141
|
+
}, [draftReason, draftValue, editing, endpoint, load, showToast]);
|
|
142
|
+
return (_jsxs(Stack, { gap: "loose", children: [_jsxs(Stack, { gap: "dense", children: [_jsx(Heading, { level: 1, children: "Pricing" }), _jsx(Text, { color: "muted", children: "Every rate the platform charges, what kind of number it is, and when it last moved. Changes take effect from the moment they are saved and never restate past orders." })] }), loadError ? (_jsxs(Stack, { surface: "danger-surface", padding: "md", rounded: "lg", gap: "dense", children: [_jsx(Text, { weight: "semibold", children: "The rate table could not be loaded." }), _jsx(Text, { size: "sm", children: loadError }), _jsx(Row, { children: _jsx(Button, { variant: "secondary", onClick: () => void load(), children: "Try again" }) })] })) : null, RATE_TIER_ORDER.map((tier) => {
|
|
143
|
+
const rows = byTier.get(tier) ?? [];
|
|
144
|
+
/*
|
|
145
|
+
* The statutory section renders with NO rows and that is the point —
|
|
146
|
+
* it tells a reader the tier exists and where it is set. Hiding an
|
|
147
|
+
* empty section would make statutory rates look like something this
|
|
148
|
+
* platform does not have.
|
|
149
|
+
*/
|
|
150
|
+
if (rows.length === 0 && tier !== "statutory")
|
|
151
|
+
return null;
|
|
152
|
+
return (_jsxs(Stack, { gap: "comfortable", children: [_jsxs(Stack, { gap: "dense", children: [_jsxs(Row, { gap: "dense", align: "center", children: [_jsx(Heading, { level: 2, children: TIER_LABEL[tier] }), _jsx(Badge, { variant: TIER_TONE[tier], children: rows.length || "set elsewhere" })] }), _jsx(Text, { size: "sm", color: "muted", children: TIER_BLURB[tier] })] }), rows.map((rate) => (_jsxs(Row, { surface: "card", padding: "card", rounded: "lg", gap: "comfortable", align: "start", justify: "between", children: [_jsxs(Stack, { gap: "dense", children: [_jsxs(Row, { gap: "dense", align: "center", children: [_jsx(Text, { weight: "semibold", children: rate.label }), rate.sellerOnly ? _jsx(Badge, { variant: "default", children: "Seller-facing" }) : null] }), _jsx(Text, { size: "sm", color: "muted", children: rate.description }), rate.lastChange ? (_jsxs(Text, { size: "xs", color: "muted", children: [formatRate(rate.lastChange.previousValue, rate.unit), " \u2192", " ", formatRate(rate.lastChange.newValue, rate.unit), " on", " ", formatWhen(rate.lastChange.effectiveFrom), rate.lastChange.changedByName ? ` by ${rate.lastChange.changedByName}` : "", rate.lastChange.reason ? ` — ${rate.lastChange.reason}` : ""] })) : (_jsx(Text, { size: "xs", color: "muted", children: "No recorded change \u2014 this is the value it was configured with." }))] }), _jsxs(Row, { gap: "comfortable", align: "center", children: [_jsx(Text, { weight: "bold", numeric: true, children: formatRate(rate.value, rate.unit) }), _jsx(Button, { variant: "secondary", size: "sm", disabled: !rate.editable, onClick: () => openEditor(rate), children: "Change" })] })] }, rate.path)))] }, tier));
|
|
153
|
+
}), _jsx(Modal, { isOpen: editing !== null, onClose: () => setEditing(null), title: editing ? `Change ${editing.label}` : "Change rate", children: editing ? (_jsx(Form, { onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "comfortable", children: [_jsx(FormErrorSummary, {}), _jsx(Text, { size: "sm", color: "muted", children: editing.description }), _jsx(FieldInput, { name: "newValue", label: `New value (${editing.unit})`, required: true, value: draftValue, onChange: setDraftValue }), _jsx(FieldTextarea, { name: "reason", label: editing.tier === "policy" ? "Reason (required)" : "Reason (optional)", value: draftReason, onChange: setDraftReason }), _jsx(Text, { size: "xs", color: "muted", children: "This takes effect now. Orders, invoices and payouts already recorded keep the rate they were charged at \u2014 nothing is restated." }), _jsxs(Row, { gap: "dense", justify: "end", children: [_jsx(Button, { variant: "ghost", onClick: () => setEditing(null), children: "Cancel" }), _jsx(Button, { action: ACTIONS.ADMIN["update-pricing"], isLoading: saving, onClick: () => {
|
|
154
|
+
clearErrors();
|
|
155
|
+
void submit(setFieldError);
|
|
156
|
+
} })] })] })) })) : null })] }));
|
|
157
|
+
}
|
|
@@ -11,7 +11,7 @@ import { ROUTES } from "../../../next/routing/route-map.js";
|
|
|
11
11
|
import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
|
|
12
12
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
|
|
13
13
|
import { safeDisplayName } from "../../../security/index.js";
|
|
14
|
-
import { Container, Div, Heading, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui/index.js";
|
|
14
|
+
import { Container, Disclaimer, Div, Heading, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui/index.js";
|
|
15
15
|
import { PreOrderDetailView } from "./PreOrderDetailView.js";
|
|
16
16
|
import { PrizeDrawBottomActions } from "./PrizeDrawBottomActions.js";
|
|
17
17
|
import { ProductTabsShell } from "./ProductTabsShell.js";
|
|
@@ -133,5 +133,5 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
|
|
|
133
133
|
price: pricePerEntry,
|
|
134
134
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
135
135
|
storeName: storeName ?? undefined,
|
|
136
|
-
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsx(OwnerQuickActions, { resource: "PRODUCT", placement: "detail", recordLabel: String(product.title ?? product.id), storeId: product.storeId ? String(product.storeId) : undefined, editHref: String(ROUTES.STORE.PRODUCTS_EDIT(String(product.id))) }), _jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: "Prize Draw" }), _jsx(Span, { size: "xs", weight: "semibold", className: `inline-block ${statusClass(revealStatus)}`, padding: "pill-sm", rounded: "full", children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsx(Div, { className: `${__P.p4}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Entries" }), _jsxs(Text, { size: "sm", weight: "semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window opens" }), _jsx(Text, { size: "sm", weight: "medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window closes" }), _jsx(Text, { size: "sm", weight: "medium", children: revealEnd.toLocaleString() })] }))] }) }), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined })), renderBuyBar: () => (_jsx(Div, { padding: "5", id: "prize-draw-buy-bar", border: "subtle", surface: "muted", rounded: "xl", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(PurchaseReviewAffordance, { productId: String(product.id), storeId: product.storeId ? String(product.storeId) : undefined }), _jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName, brandLabel: brand, storeLabel: storeName ?? undefined })] })) }), _jsx(PrizeDrawBottomActions, { pricePerEntry: pricePerEntry, currency: currency, closed: revealStatus === "closed" || remaining === 0 })] })] }));
|
|
136
|
+
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsx(OwnerQuickActions, { resource: "PRODUCT", placement: "detail", recordLabel: String(product.title ?? product.id), storeId: product.storeId ? String(product.storeId) : undefined, editHref: String(ROUTES.STORE.PRODUCTS_EDIT(String(product.id))) }), _jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: "Prize Draw" }), _jsx(Span, { size: "xs", weight: "semibold", className: `inline-block ${statusClass(revealStatus)}`, padding: "pill-sm", rounded: "full", children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsx(Div, { className: `${__P.p4}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Entries" }), _jsxs(Text, { size: "sm", weight: "semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window opens" }), _jsx(Text, { size: "sm", weight: "medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window closes" }), _jsx(Text, { size: "sm", weight: "medium", children: revealEnd.toLocaleString() })] }))] }) }), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) })), _jsx(Disclaimer, { id: "prize-draw" })] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined })), renderBuyBar: () => (_jsx(Div, { padding: "5", id: "prize-draw-buy-bar", border: "subtle", surface: "muted", rounded: "xl", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(PurchaseReviewAffordance, { productId: String(product.id), storeId: product.storeId ? String(product.storeId) : undefined }), _jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName, brandLabel: brand, storeLabel: storeName ?? undefined })] })) }), _jsx(PrizeDrawBottomActions, { pricePerEntry: pricePerEntry, currency: currency, closed: revealStatus === "closed" || remaining === 0 })] })] }));
|
|
137
137
|
}
|
|
@@ -77,5 +77,21 @@ export interface ProductDetailPageViewProps {
|
|
|
77
77
|
* Available" feature badge.
|
|
78
78
|
*/
|
|
79
79
|
emiEnabled?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Site-wide returns availability (`siteSettings.returns.windowDays > 0`),
|
|
82
|
+
* threaded from the page's data layer. Drives the "Returnable" badge.
|
|
83
|
+
*
|
|
84
|
+
* 🛑 A SITE feature gate, not a product attribute. The badge used to read
|
|
85
|
+
* `returnable={!finalSale}` — purely the product's own flag — so a platform
|
|
86
|
+
* with returns switched off still promised "Returnable" on every listing
|
|
87
|
+
* that was not a final sale. Same shape as the COD gate beside it: the
|
|
88
|
+
* product may carry the attribute, and we simply do not advertise it while
|
|
89
|
+
* the feature is off. Nothing is written to the product.
|
|
90
|
+
*
|
|
91
|
+
* Defaults to `true` when omitted so the two detail views that do not thread
|
|
92
|
+
* site settings keep their current behaviour rather than silently losing a
|
|
93
|
+
* badge they were correctly showing.
|
|
94
|
+
*/
|
|
95
|
+
returnsEnabled?: boolean;
|
|
80
96
|
}
|
|
81
|
-
export declare function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, compositionContent, productFeatures, codEnabled, emiEnabled, }: ProductDetailPageViewProps): Promise<import("react").JSX.Element>;
|
|
97
|
+
export declare function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, compositionContent, productFeatures, codEnabled, emiEnabled, returnsEnabled, }: ProductDetailPageViewProps): Promise<import("react").JSX.Element>;
|
|
@@ -79,7 +79,7 @@ function StarRating({ value }) {
|
|
|
79
79
|
// ---------------------------------------------------------------------------
|
|
80
80
|
// Component
|
|
81
81
|
// ---------------------------------------------------------------------------
|
|
82
|
-
export async function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, compositionContent, productFeatures, codEnabled, emiEnabled, }) {
|
|
82
|
+
export async function ProductDetailPageView({ slug, initialProduct, renderOfferAction, renderPrimaryActions, compositionContent, productFeatures, codEnabled, emiEnabled, returnsEnabled = true, }) {
|
|
83
83
|
// Use pre-fetched data when available to avoid a redundant repository call.
|
|
84
84
|
// The page layer wraps getProductForDetail in React.cache(), so both
|
|
85
85
|
// generateMetadata() and this component share the same in-flight promise.
|
|
@@ -386,7 +386,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
386
386
|
? "✓ In Stock"
|
|
387
387
|
: "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
|
|
388
388
|
? ` — only ${effectiveStock} left`
|
|
389
|
-
: ""] })), (categorySlugs.length > 0 || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [categorySlugs.map((slug, i) => (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryNames[i] ?? slug }, slug))), subcategory && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", transform: "capitalize", children: subcategory })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_PILL, children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, codAvailable: codEnabled === true, codHref: String(ROUTES.PUBLIC.HOW_CHECKOUT_WORKS), emiAvailable: emiEnabled === true, emiHref: String(ROUTES.PUBLIC.HOW_EMI_WORKS), condition: condition ?? undefined, returnable: !finalSale, finalSale: finalSale, labels: {
|
|
389
|
+
: ""] })), (categorySlugs.length > 0 || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [categorySlugs.map((slug, i) => (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryNames[i] ?? slug }, slug))), subcategory && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", transform: "capitalize", children: subcategory })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_PILL, children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, codAvailable: codEnabled === true, codHref: String(ROUTES.PUBLIC.HOW_CHECKOUT_WORKS), emiAvailable: emiEnabled === true, emiHref: String(ROUTES.PUBLIC.HOW_EMI_WORKS), condition: condition ?? undefined, returnable: !finalSale && returnsEnabled, finalSale: finalSale, labels: {
|
|
390
390
|
featured: "Featured",
|
|
391
391
|
fasterDelivery: "Faster Delivery",
|
|
392
392
|
ratedSeller: "Rated Seller",
|
|
@@ -603,6 +603,31 @@ export interface ProductDocument extends BaseDocument {
|
|
|
603
603
|
howToUse?: string[];
|
|
604
604
|
allowOffers?: boolean;
|
|
605
605
|
minOfferPercent?: number;
|
|
606
|
+
/**
|
|
607
|
+
* The seller permits a COORDINATED purchase on this listing.
|
|
608
|
+
*
|
|
609
|
+
* ## 🛑 A coordinated purchase is a SPECIALISED OFFER, not a listing type
|
|
610
|
+
*
|
|
611
|
+
* Owner, 2026-10-22: *"it is not a variant, it is similar to an offer, which
|
|
612
|
+
* the seller enables on the listing, then the customer can coordinate on it,
|
|
613
|
+
* it is a specialized offer."*
|
|
614
|
+
*
|
|
615
|
+
* That framing REMOVES work rather than adding it, and the removals are the
|
|
616
|
+
* part worth knowing — otherwise somebody rebuilds them:
|
|
617
|
+
*
|
|
618
|
+
* | Looks like it needs | Actually |
|
|
619
|
+
* |---|---|
|
|
620
|
+
* | a 7th `OrderSourceContext` variant | **nothing** — it is `offer-accepted`, and the provenance card already renders that branch |
|
|
621
|
+
* | an `orderType` tab | **nothing** — the Offer-wins tab already covers it |
|
|
622
|
+
* | a commit/settlement path | the offer lifecycle, with its chain fields, `checkoutDeadline` and lapse sweep |
|
|
623
|
+
* | a new money term | **none** — `unitPriceFor` already honours `lockedPrice`, so tax, payout base, refund ceiling and the invoice all follow |
|
|
624
|
+
*
|
|
625
|
+
* So this flag is deliberately shaped exactly like `allowOffers` above: same
|
|
626
|
+
* optional boolean, same seller control, same projection list. A listing
|
|
627
|
+
* with `allowOffers: false` and this `true` is coherent — the seller takes
|
|
628
|
+
* no solo offers but will consider a group — and nothing needs to forbid it.
|
|
629
|
+
*/
|
|
630
|
+
allowCoordination?: boolean;
|
|
606
631
|
customFields?: CustomField[];
|
|
607
632
|
customSections?: CustomSection[];
|
|
608
633
|
sublistingCategoryId?: string;
|
|
@@ -837,7 +862,7 @@ export declare const ProductStatusValues: {
|
|
|
837
862
|
export declare const PRODUCT_COLLECTION: "products";
|
|
838
863
|
export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "categoryIds", "featured", "listingType", "isPromoted", "isOnSale", "isSold", "searchTxt", "createdAt", "barcodeId", "physicalLocation.zone"];
|
|
839
864
|
export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
|
|
840
|
-
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["allowOffers", "minOfferPercent", "id", "title", "description", "category", "categoryIds", "categoryIdNames", "subcategory", "brand", "price", "originalPrice", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "views7d", "views30d", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "createdAt"];
|
|
865
|
+
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["allowOffers", "minOfferPercent", "allowCoordination", "id", "title", "description", "category", "categoryIds", "categoryIdNames", "subcategory", "brand", "price", "originalPrice", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "views7d", "views30d", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "createdAt"];
|
|
841
866
|
export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "categoryIds", "subcategory", "brand", "price", "originalPrice", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "physicalLocation", "sku", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "allowShipBeforeEmiComplete", "gstRate", "hsnCode", "authenticity", "printMeta", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "barcodeId"];
|
|
842
867
|
export type ProductCreateInput = Omit<ProductDocument, "id" | "createdAt" | "updatedAt" | "availableQuantity" | "bidCount" | "currentBid" | "auctionOriginalEndDate">;
|
|
843
868
|
export type ProductUpdateInput = Partial<Pick<ProductDocument, (typeof PRODUCT_UPDATABLE_FIELDS)[number]>>;
|
|
@@ -142,6 +142,8 @@ export const PRODUCT_PUBLIC_FIELDS = [
|
|
|
142
142
|
// the moment something starts projecting through it.
|
|
143
143
|
"allowOffers",
|
|
144
144
|
"minOfferPercent",
|
|
145
|
+
/* Same reasoning as `allowOffers`: the card renders a pill from it. */
|
|
146
|
+
"allowCoordination",
|
|
145
147
|
"id",
|
|
146
148
|
"title",
|
|
147
149
|
"description",
|
|
@@ -449,10 +449,10 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
449
449
|
wonByOrderId?: string | undefined;
|
|
450
450
|
}[] | undefined;
|
|
451
451
|
prizeGithubFileUrl?: string | undefined;
|
|
452
|
+
attributes?: Record<string, string> | undefined;
|
|
452
453
|
inStock?: boolean | undefined;
|
|
453
454
|
sellerAvatar?: string | undefined;
|
|
454
455
|
stockCount?: number | undefined;
|
|
455
|
-
attributes?: Record<string, string> | undefined;
|
|
456
456
|
}, {
|
|
457
457
|
status: "published" | "draft" | "in_review" | "archived";
|
|
458
458
|
id: string;
|
|
@@ -589,10 +589,10 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
589
589
|
wonByOrderId?: string | undefined;
|
|
590
590
|
}[] | undefined;
|
|
591
591
|
prizeGithubFileUrl?: string | undefined;
|
|
592
|
+
attributes?: Record<string, string> | undefined;
|
|
592
593
|
inStock?: boolean | undefined;
|
|
593
594
|
sellerAvatar?: string | undefined;
|
|
594
595
|
stockCount?: number | undefined;
|
|
595
|
-
attributes?: Record<string, string> | undefined;
|
|
596
596
|
}>;
|
|
597
597
|
/** Base Zod schema for list-query parameters. */
|
|
598
598
|
export declare const productListParamsSchema: z.ZodObject<{
|
|
@@ -28,7 +28,7 @@ export declare const productFeatureAdminCreateSchema: z.ZodObject<{
|
|
|
28
28
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
29
29
|
isActive: boolean;
|
|
30
30
|
displayOrder: number;
|
|
31
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
31
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
32
32
|
storeId?: string | undefined;
|
|
33
33
|
description?: string | undefined;
|
|
34
34
|
iconColor?: string | undefined;
|
|
@@ -39,7 +39,7 @@ export declare const productFeatureAdminCreateSchema: z.ZodObject<{
|
|
|
39
39
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
40
40
|
isActive: boolean;
|
|
41
41
|
displayOrder: number;
|
|
42
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
42
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
43
43
|
storeId?: string | undefined;
|
|
44
44
|
description?: string | undefined;
|
|
45
45
|
iconColor?: string | undefined;
|
|
@@ -60,7 +60,7 @@ export declare const productFeatureStoreCreateSchema: z.ZodObject<{
|
|
|
60
60
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
61
61
|
isActive: boolean;
|
|
62
62
|
displayOrder: number;
|
|
63
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
63
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
64
64
|
description?: string | undefined;
|
|
65
65
|
iconColor?: string | undefined;
|
|
66
66
|
}, {
|
|
@@ -69,7 +69,7 @@ export declare const productFeatureStoreCreateSchema: z.ZodObject<{
|
|
|
69
69
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
70
70
|
isActive: boolean;
|
|
71
71
|
displayOrder: number;
|
|
72
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
72
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
73
73
|
description?: string | undefined;
|
|
74
74
|
iconColor?: string | undefined;
|
|
75
75
|
}>;
|
|
@@ -91,7 +91,7 @@ export declare const productFeatureUpdateSchema: z.ZodObject<{
|
|
|
91
91
|
isActive?: boolean | undefined;
|
|
92
92
|
displayOrder?: number | undefined;
|
|
93
93
|
iconColor?: string | undefined;
|
|
94
|
-
productTypes?: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
94
|
+
productTypes?: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[] | undefined;
|
|
95
95
|
}, {
|
|
96
96
|
label?: string | undefined;
|
|
97
97
|
icon?: string | undefined;
|
|
@@ -100,7 +100,7 @@ export declare const productFeatureUpdateSchema: z.ZodObject<{
|
|
|
100
100
|
isActive?: boolean | undefined;
|
|
101
101
|
displayOrder?: number | undefined;
|
|
102
102
|
iconColor?: string | undefined;
|
|
103
|
-
productTypes?: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
103
|
+
productTypes?: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[] | undefined;
|
|
104
104
|
}>;
|
|
105
105
|
/**
|
|
106
106
|
* The FORM schema for the product-feature editor, with UI metadata.
|
|
@@ -136,7 +136,7 @@ export declare const productFeatureFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
136
136
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
137
137
|
isActive: boolean;
|
|
138
138
|
displayOrder: number;
|
|
139
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
139
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
140
140
|
storeId?: string | undefined;
|
|
141
141
|
description?: string | undefined;
|
|
142
142
|
iconColor?: string | undefined;
|
|
@@ -147,7 +147,7 @@ export declare const productFeatureFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
147
147
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
148
148
|
isActive: boolean;
|
|
149
149
|
displayOrder: number;
|
|
150
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
150
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
151
151
|
storeId?: string | undefined;
|
|
152
152
|
description?: string | undefined;
|
|
153
153
|
iconColor?: string | undefined;
|
|
@@ -158,7 +158,7 @@ export declare const productFeatureFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
158
158
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
159
159
|
isActive: boolean;
|
|
160
160
|
displayOrder: number;
|
|
161
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
161
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
162
162
|
storeId?: string | undefined;
|
|
163
163
|
description?: string | undefined;
|
|
164
164
|
iconColor?: string | undefined;
|
|
@@ -169,7 +169,7 @@ export declare const productFeatureFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
169
169
|
category: "shipping" | "seller" | "custom" | "condition" | "auction" | "platform" | "preorder";
|
|
170
170
|
isActive: boolean;
|
|
171
171
|
displayOrder: number;
|
|
172
|
-
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "
|
|
172
|
+
productTypes: ("all" | "classified" | "auction" | "prize-draw" | "digital-code" | "live" | "preorder" | "product")[];
|
|
173
173
|
storeId?: string | undefined;
|
|
174
174
|
description?: string | undefined;
|
|
175
175
|
iconColor?: string | undefined;
|
|
@@ -4,21 +4,48 @@
|
|
|
4
4
|
* A cart may hold at most one seller-scoped coupon per store, plus at most
|
|
5
5
|
* one admin (platform-wide) coupon on top. Both scopes stack with each
|
|
6
6
|
* other freely — the old `combineWithSellerCoupons` opt-out was removed
|
|
7
|
-
* (2026-08-21): stacking is now unconditional as long as the
|
|
8
|
-
* (per-store,
|
|
7
|
+
* (2026-08-21): stacking is now unconditional as long as the buckets
|
|
8
|
+
* (per-store, the single platform-wide slot, and the points-earn slot) don't
|
|
9
|
+
* collide.
|
|
10
|
+
*
|
|
11
|
+
* ## 🛑 THREE buckets since 2026-10-22, and the third is not a nicety (D51)
|
|
12
|
+
*
|
|
13
|
+
* `RIPLI05` is a PERMANENT, site-wide, undeletable coupon that awards points
|
|
14
|
+
* rather than discounting. With only the admin bucket it would occupy the one
|
|
15
|
+
* platform-wide slot forever, and **every other platform coupon would be
|
|
16
|
+
* unusable for as long as the points programme existed** — a seasonal sale
|
|
17
|
+
* coupon could never be applied by anyone.
|
|
18
|
+
*
|
|
19
|
+
* The spec also requires it to stack with a discount coupon, because it awards
|
|
20
|
+
* a percentage of the POST-discount amount paid. A design where the two cannot
|
|
21
|
+
* coexist cannot implement that sentence at all.
|
|
22
|
+
*
|
|
23
|
+
* So the bucket is keyed on the coupon's TYPE, not its scope: a `points_earn`
|
|
24
|
+
* coupon is admin-scoped like any platform coupon, and it is the type that
|
|
25
|
+
* decides which slot it competes for.
|
|
9
26
|
*
|
|
10
27
|
* Pure function, no I/O — safe to call from both the cart-apply route and
|
|
11
28
|
* the checkout re-validation pass.
|
|
12
29
|
*/
|
|
13
30
|
import type { CartAppliedCoupon } from "../../cart/schemas/firestore";
|
|
31
|
+
import type { CouponType } from "../types";
|
|
14
32
|
export interface IncomingCoupon {
|
|
15
33
|
code: string;
|
|
16
34
|
scope: "admin" | "seller";
|
|
17
35
|
storeId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The coupon's type, which decides its bucket (D51).
|
|
38
|
+
*
|
|
39
|
+
* Optional, and absence means "a discount coupon" — every cart written
|
|
40
|
+
* before `points_earn` existed carries no type on its applied-coupon
|
|
41
|
+
* entries, and treating those as discount coupons is both correct and what
|
|
42
|
+
* preserves the previous behaviour exactly.
|
|
43
|
+
*/
|
|
44
|
+
type?: CouponType;
|
|
18
45
|
}
|
|
19
46
|
/**
|
|
20
47
|
* Returns a human-readable conflict message, or null when `incoming` can be
|
|
21
48
|
* added alongside `existing` without violating the one-per-store +
|
|
22
|
-
* one-global rule.
|
|
49
|
+
* one-global + one-points-earn rule.
|
|
23
50
|
*/
|
|
24
51
|
export declare function detectCouponConflict(existing: CartAppliedCoupon[], incoming: IncomingCoupon): string | null;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/** A points-earn coupon competes for its own slot, never the admin one. */
|
|
2
|
+
function isPointsEarn(coupon) {
|
|
3
|
+
return coupon.type === "points_earn";
|
|
4
|
+
}
|
|
1
5
|
/**
|
|
2
6
|
* Returns a human-readable conflict message, or null when `incoming` can be
|
|
3
7
|
* added alongside `existing` without violating the one-per-store +
|
|
4
|
-
* one-global rule.
|
|
8
|
+
* one-global + one-points-earn rule.
|
|
5
9
|
*/
|
|
6
10
|
export function detectCouponConflict(existing, incoming) {
|
|
11
|
+
const incomingIsPoints = isPointsEarn(incoming);
|
|
7
12
|
for (const applied of existing) {
|
|
8
13
|
if (applied.code === incoming.code) {
|
|
9
14
|
return `Coupon ${incoming.code} is already applied.`;
|
|
@@ -13,7 +18,21 @@ export function detectCouponConflict(existing, incoming) {
|
|
|
13
18
|
applied.storeId === incoming.storeId) {
|
|
14
19
|
return `A coupon for this store is already applied (${applied.code}). Remove it first.`;
|
|
15
20
|
}
|
|
16
|
-
|
|
21
|
+
const appliedIsPoints = isPointsEarn(applied);
|
|
22
|
+
/*
|
|
23
|
+
* 🛑 The two admin-scoped buckets are checked SEPARATELY, and the order of
|
|
24
|
+
* these two branches does not matter precisely because each requires both
|
|
25
|
+
* sides to be in the same bucket. An earlier shape — testing scope first
|
|
26
|
+
* and type second — would have let a points coupon collide with a discount
|
|
27
|
+
* coupon, which is the whole defect.
|
|
28
|
+
*/
|
|
29
|
+
if (incomingIsPoints && appliedIsPoints) {
|
|
30
|
+
return `A points offer is already applied (${applied.code}). Remove it first.`;
|
|
31
|
+
}
|
|
32
|
+
if (!incomingIsPoints &&
|
|
33
|
+
!appliedIsPoints &&
|
|
34
|
+
incoming.scope === "admin" &&
|
|
35
|
+
applied.scope === "admin") {
|
|
17
36
|
return `Only one platform-wide coupon can be applied at a time (${applied.code}). Remove it first.`;
|
|
18
37
|
}
|
|
19
38
|
}
|
|
@@ -13,6 +13,19 @@ const TYPE_COLORS = {
|
|
|
13
13
|
fixed: { card: "bg-success-surface border-success dark:bg-success-surface dark:border-success", code: "bg-success-surface dark:bg-success-surface text-success dark:text-success border-success dark:border-success" },
|
|
14
14
|
free_shipping: { card: "bg-info-surface border-info dark:bg-info-surface dark:border-info", code: "bg-info-surface dark:bg-info-surface text-info dark:text-info border-info dark:border-info" },
|
|
15
15
|
buy_x_get_y: { card: "bg-warning-surface border-warning dark:bg-warning-surface dark:border-warning", code: "bg-warning-surface dark:bg-warning-surface text-warning dark:text-warning border-warning dark:border-warning" },
|
|
16
|
+
/*
|
|
17
|
+
* D51. A `Record<CouponType, …>`, so this entry is a COMPILE requirement
|
|
18
|
+
* rather than a nicety — which is the reason the union was widened first.
|
|
19
|
+
*
|
|
20
|
+
* 🛑 `-surface` + the matching status ink, never a literal `text-white`.
|
|
21
|
+
* A past sweep paired `bg-{status}-surface` with `text-white` on 26
|
|
22
|
+
* surfaces, and since `-surface` is a near-white tint in light themes and a
|
|
23
|
+
* dark tint in dark ones, every one of them was invisible in exactly one
|
|
24
|
+
* theme (Root Cause #67). `primary` rather than a status colour because
|
|
25
|
+
* earning points is neither a warning nor an error — it is the brand's own
|
|
26
|
+
* offer.
|
|
27
|
+
*/
|
|
28
|
+
points_earn: { card: "bg-primary-surface border-primary dark:bg-primary-surface dark:border-primary", code: "bg-primary-surface dark:bg-primary-surface text-primary dark:text-primary border-primary dark:border-primary" },
|
|
16
29
|
};
|
|
17
30
|
function pick(obj, ...keys) {
|
|
18
31
|
for (const k of keys) {
|
|
@@ -15,10 +15,10 @@ export declare const COUPON_HELP: {
|
|
|
15
15
|
readonly title: "How coupons work";
|
|
16
16
|
/** The stacking rules — the part buyers most often get wrong. */
|
|
17
17
|
readonly stackingHeading: "You can use more than one coupon";
|
|
18
|
-
readonly stackingRules: readonly ["One store coupon per store. If your cart has items from three stores, you can apply each of those stores' coupons at the same time.", "Plus one platform-wide LetItRip coupon, on top of any store coupons.", "A second coupon for the same store,
|
|
18
|
+
readonly stackingRules: readonly ["One store coupon per store. If your cart has items from three stores, you can apply each of those stores' coupons at the same time.", "Plus one platform-wide LetItRip coupon, on top of any store coupons.", "Plus one points offer, which stacks with everything else — it earns you points rather than discounting, so it never takes the place of a discount coupon.", "A second coupon for the same store, a second platform-wide coupon, or a second points offer will be declined — remove the first one to swap it."];
|
|
19
19
|
/** How the discount is actually distributed. */
|
|
20
20
|
readonly applicationHeading: "Where each discount lands";
|
|
21
|
-
readonly applicationRules: readonly ["A store coupon only discounts that store's items.", "A platform-wide coupon is spread proportionally across every store in your cart.", "Orders are placed per store, so each store's order shows its own share of the discount."];
|
|
21
|
+
readonly applicationRules: readonly ["A store coupon only discounts that store's items.", "A platform-wide coupon is spread proportionally across every store in your cart.", "Orders are placed per store, so each store's order shows its own share of the discount.", "A points offer earns points on what you pay after any discounts, so the two interact rather than ignoring each other."];
|
|
22
22
|
/** Reuses the failure reasons already written for the buyer shopping guide. */
|
|
23
23
|
readonly failureHeading: "Why a coupon might not apply";
|
|
24
24
|
readonly failureReasons: readonly ["The eligible items in your cart are below the coupon's minimum purchase amount — minimum spend is measured against the items the coupon actually covers, not your whole cart.", "The coupon has expired, or hasn't started yet.", "The coupon is limited to certain products or categories, and your cart has none of them.", "The coupon is first-order-only and you've ordered before.", "You've already used the coupon the maximum number of times allowed.", "The coupon ran out — some coupons have a total limit across all buyers."];
|
|
@@ -18,7 +18,11 @@ export const COUPON_HELP = {
|
|
|
18
18
|
stackingRules: [
|
|
19
19
|
"One store coupon per store. If your cart has items from three stores, you can apply each of those stores' coupons at the same time.",
|
|
20
20
|
"Plus one platform-wide LetItRip coupon, on top of any store coupons.",
|
|
21
|
-
|
|
21
|
+
// D51's third bucket, stated for the buyer. Without this line the rule
|
|
22
|
+
// above reads as "one platform coupon" and a buyer with a points offer
|
|
23
|
+
// applied would reasonably think it was using up that slot.
|
|
24
|
+
"Plus one points offer, which stacks with everything else — it earns you points rather than discounting, so it never takes the place of a discount coupon.",
|
|
25
|
+
"A second coupon for the same store, a second platform-wide coupon, or a second points offer will be declined — remove the first one to swap it.",
|
|
22
26
|
],
|
|
23
27
|
/** How the discount is actually distributed. */
|
|
24
28
|
applicationHeading: "Where each discount lands",
|
|
@@ -26,6 +30,10 @@ export const COUPON_HELP = {
|
|
|
26
30
|
"A store coupon only discounts that store's items.",
|
|
27
31
|
"A platform-wide coupon is spread proportionally across every store in your cart.",
|
|
28
32
|
"Orders are placed per store, so each store's order shows its own share of the discount.",
|
|
33
|
+
// Stated because the ORDER of operations is what a buyer would otherwise
|
|
34
|
+
// guess wrong — points are earned on what you actually paid, so a discount
|
|
35
|
+
// reduces the points as well as the price.
|
|
36
|
+
"A points offer earns points on what you pay after any discounts, so the two interact rather than ignoring each other.",
|
|
29
37
|
],
|
|
30
38
|
/** Reuses the failure reasons already written for the buyer shopping guide. */
|
|
31
39
|
failureHeading: "Why a coupon might not apply",
|