@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,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The integration switches, and what turning each one OFF actually does.
|
|
3
|
+
*
|
|
4
|
+
* ## 🛑 This is a VIEW over existing fields, never a second store
|
|
5
|
+
*
|
|
6
|
+
* Three of the four already have an authoritative field:
|
|
7
|
+
*
|
|
8
|
+
* payments -> `payment.razorpayEnabled`
|
|
9
|
+
* email -> `messaging.emailEnabled` (read by `guardSend`)
|
|
10
|
+
* shipping -> `shipping.provider` ("manual" | "shiprocket")
|
|
11
|
+
*
|
|
12
|
+
* Adding an `integrationSwitches.payments` beside those would be a
|
|
13
|
+
* denormalised mirror, and it would drift the first time a path wrote one of
|
|
14
|
+
* the pair and not the other (Root Cause #42) — at which point the admin page
|
|
15
|
+
* says "on" while the code that enforces it reads "off". Only `support.enabled`
|
|
16
|
+
* is new, because only support had nothing.
|
|
17
|
+
*
|
|
18
|
+
* ## 🛑 Off does not mean the same thing twice
|
|
19
|
+
*
|
|
20
|
+
* "Payments off" stops money. "Support off" hides a contact route. "Shipping
|
|
21
|
+
* off" is not even expressible — a parcel still has to move, so the shipping
|
|
22
|
+
* switch selects manual (self-ship) rather than stopping anything.
|
|
23
|
+
*
|
|
24
|
+
* Presenting four interchangeable toggles would invite an admin to flip the
|
|
25
|
+
* expensive one by muscle memory, so every row carries its own
|
|
26
|
+
* `offConsequence` and the dangerous ones are marked.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* 🛑 A `Record<IntegrationId, …>`, so a fifth integration cannot be added
|
|
30
|
+
* without writing its off-consequence. A row with no stated consequence is
|
|
31
|
+
* exactly the interchangeable-toggle failure this file exists to prevent.
|
|
32
|
+
*/
|
|
33
|
+
export const INTEGRATIONS = {
|
|
34
|
+
payments: {
|
|
35
|
+
id: "payments",
|
|
36
|
+
label: "Online payments",
|
|
37
|
+
summary: "Card, UPI and netbanking at checkout, through whichever gateway is live.",
|
|
38
|
+
/*
|
|
39
|
+
* 🛑 The second sentence is the one that matters and it was missing until
|
|
40
|
+
* the route guards were traced. `withOnlinePaymentEnabled` fails CLOSED and
|
|
41
|
+
* guards SIX routes including `/api/payment/verify` and the Razorpay
|
|
42
|
+
* WEBHOOK — so flipping this switch while a buyer is on the gateway's page
|
|
43
|
+
* abandons that payment: the money is captured and the callback that would
|
|
44
|
+
* have placed the order 404s. Razorpay retries on 5xx, NOT on 404, so it is
|
|
45
|
+
* not retried either.
|
|
46
|
+
*
|
|
47
|
+
* Ungating those two routes does not fix it — `providers.config.ts` only
|
|
48
|
+
* registers the gateway when this flag is on, so a completion path would
|
|
49
|
+
* reach `ManualPaymentProvider` and fail differently. The real fix is a
|
|
50
|
+
* bigger change (keep the provider registered for completion while refusing
|
|
51
|
+
* new orders), so until then this is a WARNING an admin must read rather
|
|
52
|
+
* than a hazard they discover.
|
|
53
|
+
*/
|
|
54
|
+
offConsequence: "Buyers can no longer pay online. Checkout falls back to the manual methods (cash, UPI with a proof upload), which need an admin to verify every payment by hand. 🛑 Payments already in progress are abandoned — a buyer part-way through the gateway may be charged without an order being placed. Switch off when nobody is checking out.",
|
|
55
|
+
severe: true,
|
|
56
|
+
// Both gateways, because the enforcing guard is an OR across them. See the
|
|
57
|
+
// note on `readIntegrationStates`.
|
|
58
|
+
field: "payment.razorpayEnabled + payment.phonepeEnabled",
|
|
59
|
+
routeGuard: "gated",
|
|
60
|
+
routeGuardNote: "withOnlinePaymentEnabled, on all six /api/payment routes. Predates this page; this page now reads the same expression so the two cannot disagree.",
|
|
61
|
+
failClosed: true,
|
|
62
|
+
},
|
|
63
|
+
email: {
|
|
64
|
+
id: "email",
|
|
65
|
+
label: "Email sending (Resend)",
|
|
66
|
+
summary: "Order, offer and account emails.",
|
|
67
|
+
offConsequence: "Customer email stops. STAFF email is deliberately unaffected — the daily digest is how an operator notices a stuck payment or a new ticket, so switching customer mail off must not blind you.",
|
|
68
|
+
severe: true,
|
|
69
|
+
field: "messaging.emailEnabled",
|
|
70
|
+
routeGuard: "none",
|
|
71
|
+
/*
|
|
72
|
+
* 🛑 Not an oversight, and not a gap to close later. Email has no inbound
|
|
73
|
+
* route to guard — it is an OUTBOUND effect, and `guardSend` already
|
|
74
|
+
* enforces this flag at the single place that sends. Adding a route guard
|
|
75
|
+
* would mean inventing a route to guard.
|
|
76
|
+
*/
|
|
77
|
+
routeGuardNote: "Nothing to gate: email is outbound. guardSend() enforces this flag at sendEmail(), the only sender.",
|
|
78
|
+
failClosed: false,
|
|
79
|
+
},
|
|
80
|
+
shipping: {
|
|
81
|
+
id: "shipping",
|
|
82
|
+
label: "Courier booking (Shiprocket)",
|
|
83
|
+
summary: "We book the courier and pay the freight.",
|
|
84
|
+
/*
|
|
85
|
+
* Not "shipping stops". The switch selects the PROVIDER, and manual is a
|
|
86
|
+
* working path, not a degraded one — it is the self-ship flow sellers
|
|
87
|
+
* already use and claim postage back through.
|
|
88
|
+
*/
|
|
89
|
+
offConsequence: "We stop booking couriers. Sellers ship themselves and claim the postage back — the flow they already have. Nothing stops moving; in-flight shipments are unaffected.",
|
|
90
|
+
severe: false,
|
|
91
|
+
field: "shipping.provider",
|
|
92
|
+
routeGuard: "none",
|
|
93
|
+
/*
|
|
94
|
+
* 🛑 `/api/shipping/quote` must stay OPEN when this is off, and that is the
|
|
95
|
+
* whole reason this field exists rather than a blanket wrapper over every
|
|
96
|
+
* integration's routes.
|
|
97
|
+
*
|
|
98
|
+
* "Off" here means `provider: "manual"`, which is a WORKING path, not a
|
|
99
|
+
* disabled one. 404ing the quote route would break the self-ship flow that
|
|
100
|
+
* switching the integration off is specifically selecting — the guard would
|
|
101
|
+
* take out the fallback it is falling back to.
|
|
102
|
+
*/
|
|
103
|
+
routeGuardNote: "Deliberately ungated: off means provider=manual, a working path. Guarding /api/shipping/quote would break the self-ship flow this switch selects.",
|
|
104
|
+
failClosed: false,
|
|
105
|
+
},
|
|
106
|
+
support: {
|
|
107
|
+
id: "support",
|
|
108
|
+
label: "Support & tickets",
|
|
109
|
+
summary: "The buyer's contact form and ticket history.",
|
|
110
|
+
/*
|
|
111
|
+
* The owner chose the widest reading of "off", and the consequence is real
|
|
112
|
+
* enough that the row has to state it plainly rather than let an admin
|
|
113
|
+
* discover it.
|
|
114
|
+
*/
|
|
115
|
+
offConsequence: "The ticket surface disappears for buyers — no new tickets, no ticket list, no nav entry. The public Help Centre and its FAQs stay up, because self-service is what reduces tickets. Staff keep full access, so open tickets are still workable. A buyer with a conversation in progress loses sight of it.",
|
|
116
|
+
severe: true,
|
|
117
|
+
field: "support.enabled",
|
|
118
|
+
routeGuard: "gated",
|
|
119
|
+
/*
|
|
120
|
+
* 🛑 The line this note draws is RAISING vs ANSWERING, not buyer vs staff,
|
|
121
|
+
* and it decides three surfaces — so it is written down rather than left to
|
|
122
|
+
* be re-derived by whoever next "completes" the gating.
|
|
123
|
+
*
|
|
124
|
+
* GATED (raising): `/api/support/tickets` GET and POST, and a server layout
|
|
125
|
+
* over all of `/user/support/*`. The GET too, because gating only the write
|
|
126
|
+
* leaves a buyer looking at a ticket list they cannot reply to.
|
|
127
|
+
*
|
|
128
|
+
* NOT GATED (answering): `/admin/support-tickets`, and `/store/support` —
|
|
129
|
+
* which is the seller reading tickets raised ABOUT their store, not filing
|
|
130
|
+
* them. It is served by `SELLER_ENDPOINTS.SUPPORT`, a store-scoped endpoint,
|
|
131
|
+
* so it is untouched by the gate above and that is correct: the owner's rule
|
|
132
|
+
* is that open tickets stay workable, and a seller answering one is on the
|
|
133
|
+
* same side of that line as staff.
|
|
134
|
+
*
|
|
135
|
+
* EXEMPT: `unban_request`, the formal appeal channel. It already bypasses
|
|
136
|
+
* the soft-ban guard and the ticket cap, and an integration toggle must not
|
|
137
|
+
* be able to close a channel a soft ban is explicitly forbidden from
|
|
138
|
+
* closing.
|
|
139
|
+
*/
|
|
140
|
+
routeGuardNote: "Gated on the RAISING side only: /api/support/tickets (GET+POST) and a server layout over /user/support/*. /store/support and /admin/support-tickets stay open — answering, not raising. unban_request is exempt.",
|
|
141
|
+
failClosed: false,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
/** Display order. Money first, because it is what an admin came to check. */
|
|
145
|
+
export const INTEGRATION_ORDER = [
|
|
146
|
+
"payments",
|
|
147
|
+
"email",
|
|
148
|
+
"shipping",
|
|
149
|
+
"support",
|
|
150
|
+
];
|
|
151
|
+
/**
|
|
152
|
+
* Read each switch out of a settings document.
|
|
153
|
+
*
|
|
154
|
+
* 🛑 Defaults differ per integration and are NOT a uniform `?? false`:
|
|
155
|
+
* `razorpayEnabled` genuinely defaults off (it is opt-in and the account runs
|
|
156
|
+
* test keys), while email and support default ON because an older settings
|
|
157
|
+
* document with no such field must not read as "we stopped talking to
|
|
158
|
+
* customers".
|
|
159
|
+
*
|
|
160
|
+
* 🛑 THIS FUNCTION IS THE EXPRESSION THE ROUTE GUARDS USE. `withOnlinePaymentEnabled`
|
|
161
|
+
* had hand-written the payments half, and the first version of this file
|
|
162
|
+
* reproduced it WRONG — see the payments line. Both now resolve here, so the
|
|
163
|
+
* admin page and the guard cannot disagree about whether an integration is on.
|
|
164
|
+
* That disagreement is the entire failure mode this file's header warns about
|
|
165
|
+
* (Root Cause #42), and it had already happened once, inside the file.
|
|
166
|
+
*/
|
|
167
|
+
export function readIntegrationStates(settings) {
|
|
168
|
+
return {
|
|
169
|
+
/*
|
|
170
|
+
* 🛑 An OR across BOTH gateways, not `razorpayEnabled` alone.
|
|
171
|
+
*
|
|
172
|
+
* This was wrong when written, and wrong in the direction that matters: the
|
|
173
|
+
* route guard is `razorpayEnabled === true || phonepeEnabled === true`, so
|
|
174
|
+
* with PhonePe on and Razorpay off the page would have reported "payments
|
|
175
|
+
* OFF" while all six /api/payment routes were OPEN and taking money. An
|
|
176
|
+
* admin reading that row would believe they had stopped payments.
|
|
177
|
+
*
|
|
178
|
+
* The guard's own docstring explains why it is an OR and must stay one:
|
|
179
|
+
* these routes are provider-AGNOSTIC — they call `getProviders().payment`,
|
|
180
|
+
* whichever single gateway `providers.config.ts` registered. The question
|
|
181
|
+
* is "is an online gateway live at all", asked once.
|
|
182
|
+
*/
|
|
183
|
+
payments: {
|
|
184
|
+
enabled: settings?.payment?.razorpayEnabled === true ||
|
|
185
|
+
settings?.payment?.phonepeEnabled === true,
|
|
186
|
+
},
|
|
187
|
+
email: { enabled: settings?.messaging?.emailEnabled !== false },
|
|
188
|
+
// "Enabled" means we are booking couriers, i.e. the provider is not manual.
|
|
189
|
+
shipping: { enabled: settings?.shipping?.provider === "shiprocket" },
|
|
190
|
+
support: { enabled: settings?.support?.enabled !== false },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function integrationPatch(id, enabled) {
|
|
194
|
+
switch (id) {
|
|
195
|
+
case "payments":
|
|
196
|
+
/*
|
|
197
|
+
* 🛑 Writes BOTH gateway flags, and the OFF case is why.
|
|
198
|
+
*
|
|
199
|
+
* The guard is an OR, so clearing only `razorpayEnabled` while PhonePe
|
|
200
|
+
* was on would leave every /api/payment route open — the switch would
|
|
201
|
+
* report success and change nothing. "Disable payments" has to mean no
|
|
202
|
+
* online gateway is live, which is one flag per gateway.
|
|
203
|
+
*
|
|
204
|
+
* The ON case re-enables RAZORPAY only, deliberately asymmetric:
|
|
205
|
+
* `providers.config.ts` prefers Razorpay and ignores PhonePe when both
|
|
206
|
+
* are true (it logs exactly that), so turning both on would set a flag
|
|
207
|
+
* with no effect and make the page claim two gateways where there is
|
|
208
|
+
* one. Choosing a gateway is a different decision from switching
|
|
209
|
+
* payments on, and it belongs in Site Settings → Payment.
|
|
210
|
+
*/
|
|
211
|
+
return enabled
|
|
212
|
+
? { payment: { razorpayEnabled: true } }
|
|
213
|
+
: { payment: { razorpayEnabled: false, phonepeEnabled: false } };
|
|
214
|
+
case "email":
|
|
215
|
+
return { messaging: { emailEnabled: enabled } };
|
|
216
|
+
case "shipping":
|
|
217
|
+
/*
|
|
218
|
+
* 🛑 `manual`, never a missing value. The field's own docstring is
|
|
219
|
+
* explicit that a provider which fails to resolve must not silently
|
|
220
|
+
* become manual — so the OFF state has to be manual EXPLICITLY, and the
|
|
221
|
+
* absence of a value must never be how we express it.
|
|
222
|
+
*/
|
|
223
|
+
return { shipping: { provider: enabled ? "shiprocket" : "manual" } };
|
|
224
|
+
case "support":
|
|
225
|
+
return { support: { enabled } };
|
|
226
|
+
default: {
|
|
227
|
+
/*
|
|
228
|
+
* Exhaustive. A fifth integration will not compile until someone decides
|
|
229
|
+
* which field it writes — which is the one decision that cannot be
|
|
230
|
+
* guessed later from the switch alone.
|
|
231
|
+
*/
|
|
232
|
+
const unreachable = id;
|
|
233
|
+
return unreachable;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Every patch for a bulk enable/disable, merged into one object. */
|
|
238
|
+
export function allIntegrationsPatch(enabled) {
|
|
239
|
+
const merged = {};
|
|
240
|
+
for (const id of INTEGRATION_ORDER) {
|
|
241
|
+
const patch = integrationPatch(id, enabled);
|
|
242
|
+
/*
|
|
243
|
+
* Merged per GROUP, so two integrations writing different fields of the
|
|
244
|
+
* same group (none do today, but `payment` already carries two flags)
|
|
245
|
+
* cannot clobber each other.
|
|
246
|
+
*
|
|
247
|
+
* Spelled out per key rather than via `Object.entries`, which erased the
|
|
248
|
+
* types this patch now carries — and listing the groups is also a forcing
|
|
249
|
+
* function: a fifth integration writing a NEW group does not silently
|
|
250
|
+
* vanish from the bulk switch, it fails to merge until someone adds the
|
|
251
|
+
* line. Same argument as the exhaustive `never` in `integrationPatch`.
|
|
252
|
+
*/
|
|
253
|
+
if (patch.payment)
|
|
254
|
+
merged.payment = { ...merged.payment, ...patch.payment };
|
|
255
|
+
if (patch.messaging)
|
|
256
|
+
merged.messaging = { ...merged.messaging, ...patch.messaging };
|
|
257
|
+
if (patch.shipping)
|
|
258
|
+
merged.shipping = { ...merged.shipping, ...patch.shipping };
|
|
259
|
+
if (patch.support)
|
|
260
|
+
merged.support = { ...merged.support, ...patch.support };
|
|
261
|
+
}
|
|
262
|
+
return merged;
|
|
263
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard hub sections — one hub per nav group, derived from the nav itself.
|
|
3
|
+
*
|
|
4
|
+
* ## What a hub is
|
|
5
|
+
*
|
|
6
|
+
* Each dashboard (`/admin`, `/store`, `/user`) has a landing page listing its
|
|
7
|
+
* SECTIONS, and each section has a hub listing the SCREENS in it. Twelve admin
|
|
8
|
+
* sections, six store, four user — 22 hubs, served by three dynamic routes
|
|
9
|
+
* (`/{portal}/sections/[section]`), because a hub has no content of its own
|
|
10
|
+
* beyond the group it names.
|
|
11
|
+
*
|
|
12
|
+
* ## 🛑 Everything here is DERIVED. There is no second list.
|
|
13
|
+
*
|
|
14
|
+
* `ADMIN_NAV_GROUPS` / `STORE_NAV_GROUPS` / `USER_NAV_GROUPS` already carry a
|
|
15
|
+
* title per group and, per item, an href, a label, a `description` ("what this
|
|
16
|
+
* screen is FOR, in the user's words") and `keywords`. That is the whole card:
|
|
17
|
+
* a hub needs no copy of its own, and must not have any.
|
|
18
|
+
*
|
|
19
|
+
* The alternative was a hand-written array of 22 section descriptors, which is
|
|
20
|
+
* Root Cause #61 pre-written — ten independent enumerations of one union, nine
|
|
21
|
+
* of which silently did not grow. `AdminGuideHubView` is the live warning: it
|
|
22
|
+
* is already a hub, it is the visual pattern this follows, and it hand-writes
|
|
23
|
+
* its card list, so a guide added to the nav does not appear on it.
|
|
24
|
+
*
|
|
25
|
+
* ## The slug
|
|
26
|
+
*
|
|
27
|
+
* `hubSectionSlug` is the only mapping between a group title and its URL. It is
|
|
28
|
+
* lossy by construction ("Trust & Safety" → `trust-safety`), so two titles
|
|
29
|
+
* CAN collide — and a collision makes one hub permanently unreachable while
|
|
30
|
+
* the other answers for both, with nothing erroring.
|
|
31
|
+
*
|
|
32
|
+
* That is checked statically rather than defended at runtime:
|
|
33
|
+
* `audit-dashboard-hub-coverage` fails on a duplicate slug within a portal. A
|
|
34
|
+
* runtime throw would turn a naming choice into a 500 on a live dashboard, and
|
|
35
|
+
* a silent de-dupe would hide exactly the state the audit exists to name.
|
|
36
|
+
*/
|
|
37
|
+
import type { SidebarNavGroup, SidebarNavItem } from "../layout/types";
|
|
38
|
+
/** One section of a dashboard: a nav group plus the slug its hub answers on. */
|
|
39
|
+
export interface DashboardHubSection {
|
|
40
|
+
/** URL segment — derived from `title`, never authored. */
|
|
41
|
+
slug: string;
|
|
42
|
+
/** The group's own title, rendered verbatim as the section heading. */
|
|
43
|
+
title: string;
|
|
44
|
+
/** The screens in this section, already permission-filtered by the caller. */
|
|
45
|
+
items: SidebarNavItem[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A group title as a URL segment.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately the same shape as `navItemId`'s path normalisation — lower-case,
|
|
51
|
+
* non-alphanumerics collapsed to a single hyphen, no leading or trailing
|
|
52
|
+
* hyphen. `&` and `/` carry no meaning in a slug, so "Trust & Safety" and
|
|
53
|
+
* "Orders & Reviews" become `trust-safety` and `orders-reviews`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function hubSectionSlug(title: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Every section of a dashboard, in nav order.
|
|
58
|
+
*
|
|
59
|
+
* 🛑 Pass groups that are ALREADY permission-filtered (`filterNavGroups`). This
|
|
60
|
+
* function deliberately applies no filter of its own: two places deciding
|
|
61
|
+
* visibility is how a hub ends up offering a card the sidebar hides, and the
|
|
62
|
+
* sidebar's answer is the one the route guards agree with.
|
|
63
|
+
*/
|
|
64
|
+
export declare function hubSections(groups: readonly SidebarNavGroup[]): DashboardHubSection[];
|
|
65
|
+
/**
|
|
66
|
+
* The section a `[section]` route segment names, or `undefined`.
|
|
67
|
+
*
|
|
68
|
+
* `undefined` is the caller's cue to `notFound()`. It must not fall back to the
|
|
69
|
+
* first section: a mistyped or retired slug would then render a real page under
|
|
70
|
+
* the wrong heading, which is indistinguishable from a working link.
|
|
71
|
+
*/
|
|
72
|
+
export declare function findHubSection(groups: readonly SidebarNavGroup[], slug: string): DashboardHubSection | undefined;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard hub sections — one hub per nav group, derived from the nav itself.
|
|
3
|
+
*
|
|
4
|
+
* ## What a hub is
|
|
5
|
+
*
|
|
6
|
+
* Each dashboard (`/admin`, `/store`, `/user`) has a landing page listing its
|
|
7
|
+
* SECTIONS, and each section has a hub listing the SCREENS in it. Twelve admin
|
|
8
|
+
* sections, six store, four user — 22 hubs, served by three dynamic routes
|
|
9
|
+
* (`/{portal}/sections/[section]`), because a hub has no content of its own
|
|
10
|
+
* beyond the group it names.
|
|
11
|
+
*
|
|
12
|
+
* ## 🛑 Everything here is DERIVED. There is no second list.
|
|
13
|
+
*
|
|
14
|
+
* `ADMIN_NAV_GROUPS` / `STORE_NAV_GROUPS` / `USER_NAV_GROUPS` already carry a
|
|
15
|
+
* title per group and, per item, an href, a label, a `description` ("what this
|
|
16
|
+
* screen is FOR, in the user's words") and `keywords`. That is the whole card:
|
|
17
|
+
* a hub needs no copy of its own, and must not have any.
|
|
18
|
+
*
|
|
19
|
+
* The alternative was a hand-written array of 22 section descriptors, which is
|
|
20
|
+
* Root Cause #61 pre-written — ten independent enumerations of one union, nine
|
|
21
|
+
* of which silently did not grow. `AdminGuideHubView` is the live warning: it
|
|
22
|
+
* is already a hub, it is the visual pattern this follows, and it hand-writes
|
|
23
|
+
* its card list, so a guide added to the nav does not appear on it.
|
|
24
|
+
*
|
|
25
|
+
* ## The slug
|
|
26
|
+
*
|
|
27
|
+
* `hubSectionSlug` is the only mapping between a group title and its URL. It is
|
|
28
|
+
* lossy by construction ("Trust & Safety" → `trust-safety`), so two titles
|
|
29
|
+
* CAN collide — and a collision makes one hub permanently unreachable while
|
|
30
|
+
* the other answers for both, with nothing erroring.
|
|
31
|
+
*
|
|
32
|
+
* That is checked statically rather than defended at runtime:
|
|
33
|
+
* `audit-dashboard-hub-coverage` fails on a duplicate slug within a portal. A
|
|
34
|
+
* runtime throw would turn a naming choice into a 500 on a live dashboard, and
|
|
35
|
+
* a silent de-dupe would hide exactly the state the audit exists to name.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* A group title as a URL segment.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately the same shape as `navItemId`'s path normalisation — lower-case,
|
|
41
|
+
* non-alphanumerics collapsed to a single hyphen, no leading or trailing
|
|
42
|
+
* hyphen. `&` and `/` carry no meaning in a slug, so "Trust & Safety" and
|
|
43
|
+
* "Orders & Reviews" become `trust-safety` and `orders-reviews`.
|
|
44
|
+
*/
|
|
45
|
+
export function hubSectionSlug(title) {
|
|
46
|
+
return title
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
49
|
+
.replace(/^-+|-+$/g, "");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Every section of a dashboard, in nav order.
|
|
53
|
+
*
|
|
54
|
+
* 🛑 Pass groups that are ALREADY permission-filtered (`filterNavGroups`). This
|
|
55
|
+
* function deliberately applies no filter of its own: two places deciding
|
|
56
|
+
* visibility is how a hub ends up offering a card the sidebar hides, and the
|
|
57
|
+
* sidebar's answer is the one the route guards agree with.
|
|
58
|
+
*/
|
|
59
|
+
export function hubSections(groups) {
|
|
60
|
+
return groups.map((group) => ({
|
|
61
|
+
slug: hubSectionSlug(group.title),
|
|
62
|
+
title: group.title,
|
|
63
|
+
items: group.items,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The section a `[section]` route segment names, or `undefined`.
|
|
68
|
+
*
|
|
69
|
+
* `undefined` is the caller's cue to `notFound()`. It must not fall back to the
|
|
70
|
+
* first section: a mistyped or retired slug would then render a real page under
|
|
71
|
+
* the wrong heading, which is indistinguishable from a working link.
|
|
72
|
+
*/
|
|
73
|
+
export function findHubSection(groups, slug) {
|
|
74
|
+
return hubSections(groups).find((section) => section.slug === slug);
|
|
75
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMI is an OPT-IN ON THE ORDER, not a way of paying.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Until 2026-10-22 it was a sixth `paymentMethod`, and that conflated two
|
|
5
|
+
* independent decisions: *do I want to spread this over months* and *how does
|
|
6
|
+
* the money reach you*. A buyer who wanted instalments had, by picking "Pay in
|
|
7
|
+
* EMI", also implicitly chosen the tender — and the tender they got was the
|
|
8
|
+
* manual bank/UPI proof flow, because `MANUAL_PAYMENT_METHODS` is
|
|
9
|
+
* `["cash","upi_manual","emi"]` and `hasPaymentWindow` tested for `EMI`
|
|
10
|
+
* alongside the other two. Nothing in the UI said so.
|
|
11
|
+
*
|
|
12
|
+
* The two axes are now separate: `emiOptIn` + `emiTenureMonths` describe the
|
|
13
|
+
* arrangement, `paymentMethod` describes the tender for the token. Everything
|
|
14
|
+
* downstream — the schedule, the surcharge, the order fields — keys on
|
|
15
|
+
* `emiOptIn`, so an EMI order can be paid online or by hand.
|
|
16
|
+
*
|
|
17
|
+
* ── Why the legacy value is MAPPED rather than rejected ────────────────────
|
|
18
|
+
*
|
|
19
|
+
* A browser tab opened before this change still posts `paymentMethod: "emi"`.
|
|
20
|
+
* Mapping it to `{ tender: "upi_manual", emiOptIn: true }` is not a guess: it
|
|
21
|
+
* is exactly what that request did before, measured at
|
|
22
|
+
* `actions.ts`'s `hasPaymentWindow` (a 15-minute window plus a displayed UPI
|
|
23
|
+
* id — i.e. the manual-proof flow) and at `MANUAL_PAYMENT_METHODS`. So the
|
|
24
|
+
* stale tab keeps behaving identically instead of 400ing at the one moment a
|
|
25
|
+
* buyer is trying to pay.
|
|
26
|
+
*
|
|
27
|
+
* ── Why this returns a RESULT rather than throwing ─────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* The same answer has to serve two callers with different jobs: the order
|
|
30
|
+
* action, which must refuse, and the pricing preview, which must still price
|
|
31
|
+
* *something* rather than 500 while a buyer fiddles with a radio. A plain
|
|
32
|
+
* value lets each decide.
|
|
33
|
+
*/
|
|
34
|
+
import type { CheckoutPaymentMethod } from "../checkout/config";
|
|
35
|
+
/** The tender — `emi` is excluded by construction, which is the whole point. */
|
|
36
|
+
export type EmiTender = Exclude<CheckoutPaymentMethod, "emi">;
|
|
37
|
+
export interface EmiChoiceRequest {
|
|
38
|
+
paymentMethod: CheckoutPaymentMethod;
|
|
39
|
+
/**
|
|
40
|
+
* The DURABLE selection, read off `CartDocument.emiPlan`.
|
|
41
|
+
*
|
|
42
|
+
* 🛑 Not a request-body field, and that is the point: the online path
|
|
43
|
+
* decides the capture amount in one request and writes the order in
|
|
44
|
+
* another, so a body-carried choice cannot be read by both. See the schema
|
|
45
|
+
* note on `CartDocument.emiPlan`.
|
|
46
|
+
*/
|
|
47
|
+
cartEmiPlan?: {
|
|
48
|
+
tenureMonths: number;
|
|
49
|
+
} | null;
|
|
50
|
+
/**
|
|
51
|
+
* Legacy only: a tab opened before 2026-10-22 posts
|
|
52
|
+
* `paymentMethod: "emi"` plus a tenure in the body, with nothing on the
|
|
53
|
+
* cart. Honoured so that buyer is not 400'd mid-payment, and ignored
|
|
54
|
+
* whenever the cart carries a plan.
|
|
55
|
+
*/
|
|
56
|
+
legacyTenureMonths?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface EmiChoice {
|
|
59
|
+
paymentMethod: EmiTender;
|
|
60
|
+
emiOptIn: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* 🛑 Set ONLY when `emiOptIn` is true. A tenure left over from a radio the
|
|
63
|
+
* buyer un-ticked must not reach `computeBuyerEmiQuote`, or they are
|
|
64
|
+
* surcharged for an arrangement they declined.
|
|
65
|
+
*/
|
|
66
|
+
emiTenureMonths?: number;
|
|
67
|
+
}
|
|
68
|
+
export type EmiChoiceResult = {
|
|
69
|
+
ok: true;
|
|
70
|
+
choice: EmiChoice;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
reason: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Normalise a checkout request's payment axes. **Every** caller that reads
|
|
77
|
+
* `paymentMethod` or decides whether to build an EMI schedule must resolve
|
|
78
|
+
* through this — two copies of "is this an EMI order" is the drift that this
|
|
79
|
+
* function exists to end (Root Cause #75).
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* The TENDER alone — a pure function of the request, needing no cart.
|
|
83
|
+
*
|
|
84
|
+
* Split out because the early checkout guards (is COD enabled, is manual UPI
|
|
85
|
+
* accepted) run *before* the cart is read and must already be talking about
|
|
86
|
+
* the real tender. `resolveEmiChoice` calls this, so the legacy mapping has
|
|
87
|
+
* one definition rather than two that agree today.
|
|
88
|
+
*/
|
|
89
|
+
export declare function emiTenderFor(method: CheckoutPaymentMethod): EmiTender;
|
|
90
|
+
export declare function resolveEmiChoice(request: EmiChoiceRequest): EmiChoiceResult;
|
|
91
|
+
/**
|
|
92
|
+
* The tenders a buyer may pick while opted in, for disabling a control rather
|
|
93
|
+
* than letting them pick it and be refused at submit. Derived from the same
|
|
94
|
+
* map the refusal reads, so the affordance and the rule cannot disagree.
|
|
95
|
+
*/
|
|
96
|
+
export declare function isTenderCompatibleWithEmi(method: EmiTender): boolean;
|
|
97
|
+
/** Why a tender is unavailable while instalments are on. `null` when it is available. */
|
|
98
|
+
export declare function emiTenderBlockReason(method: EmiTender): string | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** The tender a pre-2026-10-22 `paymentMethod: "emi"` request actually got. */
|
|
2
|
+
const EMI_LEGACY_TENDER = "upi_manual";
|
|
3
|
+
/**
|
|
4
|
+
* Tenders that cannot carry an instalment plan, with the reason stated rather
|
|
5
|
+
* than the combination silently dropped.
|
|
6
|
+
*
|
|
7
|
+
* - `cod` collects the balance at the door; "the balance" is precisely what an
|
|
8
|
+
* instalment plan is for. The two describe opposite settlements of the same
|
|
9
|
+
* money.
|
|
10
|
+
* - `admin_bypass` marks an order paid without a payment. The existing order
|
|
11
|
+
* writer already nulls every `emi*` field under `adminBypass`, so the
|
|
12
|
+
* combination has never produced an EMI order — this refuses it out loud
|
|
13
|
+
* instead of producing a plain order that looks like the EMI one the tester
|
|
14
|
+
* asked for.
|
|
15
|
+
*/
|
|
16
|
+
const EMI_INCOMPATIBLE = {
|
|
17
|
+
cod: "Cash on delivery collects the balance when the parcel arrives, so it cannot also be paid in monthly instalments. Pay the token online or by bank/UPI transfer instead.",
|
|
18
|
+
admin_bypass: "The admin bypass marks an order paid without taking a payment, so there is no token to collect and no instalment plan to start.",
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Normalise a checkout request's payment axes. **Every** caller that reads
|
|
22
|
+
* `paymentMethod` or decides whether to build an EMI schedule must resolve
|
|
23
|
+
* through this — two copies of "is this an EMI order" is the drift that this
|
|
24
|
+
* function exists to end (Root Cause #75).
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* The TENDER alone — a pure function of the request, needing no cart.
|
|
28
|
+
*
|
|
29
|
+
* Split out because the early checkout guards (is COD enabled, is manual UPI
|
|
30
|
+
* accepted) run *before* the cart is read and must already be talking about
|
|
31
|
+
* the real tender. `resolveEmiChoice` calls this, so the legacy mapping has
|
|
32
|
+
* one definition rather than two that agree today.
|
|
33
|
+
*/
|
|
34
|
+
export function emiTenderFor(method) {
|
|
35
|
+
return method === "emi" ? EMI_LEGACY_TENDER : method;
|
|
36
|
+
}
|
|
37
|
+
export function resolveEmiChoice(request) {
|
|
38
|
+
const legacy = request.paymentMethod === "emi";
|
|
39
|
+
const paymentMethod = emiTenderFor(request.paymentMethod);
|
|
40
|
+
/*
|
|
41
|
+
* The cart WINS. A stale tab's body tenure is a fallback for the one case
|
|
42
|
+
* where there is nothing on the cart to read — never an override, or a
|
|
43
|
+
* client could re-price an order the server has already quoted.
|
|
44
|
+
*/
|
|
45
|
+
const tenureMonths = request.cartEmiPlan?.tenureMonths ?? (legacy ? request.legacyTenureMonths : undefined);
|
|
46
|
+
const emiOptIn = tenureMonths !== undefined;
|
|
47
|
+
if (!emiOptIn) {
|
|
48
|
+
return { ok: true, choice: { paymentMethod, emiOptIn: false } };
|
|
49
|
+
}
|
|
50
|
+
const incompatible = EMI_INCOMPATIBLE[paymentMethod];
|
|
51
|
+
if (incompatible)
|
|
52
|
+
return { ok: false, reason: incompatible };
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
choice: { paymentMethod, emiOptIn: true, emiTenureMonths: tenureMonths },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The tenders a buyer may pick while opted in, for disabling a control rather
|
|
60
|
+
* than letting them pick it and be refused at submit. Derived from the same
|
|
61
|
+
* map the refusal reads, so the affordance and the rule cannot disagree.
|
|
62
|
+
*/
|
|
63
|
+
export function isTenderCompatibleWithEmi(method) {
|
|
64
|
+
return EMI_INCOMPATIBLE[method] === undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Why a tender is unavailable while instalments are on. `null` when it is available. */
|
|
67
|
+
export function emiTenderBlockReason(method) {
|
|
68
|
+
return EMI_INCOMPATIBLE[method] ?? null;
|
|
69
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type BuyerEmiQuote, type EmiScheduleResult, type EmiSettings } from "./schedule";
|
|
2
|
+
/** What every caller knows about one seller-group at the moment EMI is priced. */
|
|
3
|
+
export interface GroupEmiInput {
|
|
4
|
+
/** Already normalised by `resolveEmiChoice` — never derived from a tender. */
|
|
5
|
+
emiOptIn: boolean;
|
|
6
|
+
emiTenureMonths?: number;
|
|
7
|
+
/** The group's GOODS subtotal, before coupon and before any fee. */
|
|
8
|
+
groupSubtotal: number;
|
|
9
|
+
/** `StoreDocument.emiEnabled` for this group's seller. */
|
|
10
|
+
sellerEmiEnabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The gate, shared so "does EMI apply to this group" is answered identically
|
|
14
|
+
* everywhere.
|
|
15
|
+
*
|
|
16
|
+
* 🛑 Returns a REASON rather than a boolean, because the two callers do
|
|
17
|
+
* opposite things with it: order creation must refuse (an ineligible EMI order
|
|
18
|
+
* that silently becomes a full-price order is money the buyer did not agree
|
|
19
|
+
* to), while the preview must carry on and simply price no surcharge (a
|
|
20
|
+
* preview that throws blanks the Order Summary while the buyer is still
|
|
21
|
+
* choosing). A boolean would have forced each to re-derive the reason.
|
|
22
|
+
*/
|
|
23
|
+
export type GroupEmiGate = {
|
|
24
|
+
applies: true;
|
|
25
|
+
} | {
|
|
26
|
+
applies: false;
|
|
27
|
+
reason: "not_opted_in" | "no_tenure" | "no_settings" | string;
|
|
28
|
+
};
|
|
29
|
+
export declare function groupEmiGate(input: GroupEmiInput, settings: Pick<EmiSettings, "enabled" | "minOrderValue"> | null | undefined): GroupEmiGate;
|
|
30
|
+
/**
|
|
31
|
+
* The buyer-visible quote for one group, or `null` when EMI does not apply.
|
|
32
|
+
*
|
|
33
|
+
* For the preview and for the gateway CAPTURE amount — neither needs the
|
|
34
|
+
* platform/seller surcharge split, and neither should be able to print it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function groupEmiQuote(input: GroupEmiInput, settings: (Pick<EmiSettings, "enabled" | "minOrderValue" | "tokenPercent" | "billingDay" | "surchargePercentPerMonth">) | null | undefined, purchaseDate?: Date): BuyerEmiQuote | null;
|
|
37
|
+
/**
|
|
38
|
+
* The server-side schedule for one group, including the surcharge split that
|
|
39
|
+
* is persisted onto the order. For the two order WRITERS only.
|
|
40
|
+
*/
|
|
41
|
+
export declare function groupEmiSchedule(input: GroupEmiInput, settings: EmiSettings | null | undefined, purchaseDate?: Date): EmiScheduleResult | null;
|
|
42
|
+
/**
|
|
43
|
+
* What is actually collected at checkout: the order total, less any points
|
|
44
|
+
* tendered, less whatever has been deferred into instalments.
|
|
45
|
+
*
|
|
46
|
+
* 🛑 Composes with the points tender rather than replacing it. Both reduce
|
|
47
|
+
* what is collected and they are independent, so an EMI order part-funded by
|
|
48
|
+
* points must subtract both — writing `total − remainingBalance` would hand
|
|
49
|
+
* back the points discount, and `total − pointsApplied` alone is the bug this
|
|
50
|
+
* function exists to fix. Floored at zero: points can cover at most half an
|
|
51
|
+
* order, so the sum cannot genuinely go negative, and clamping means a future
|
|
52
|
+
* change to that cap degrades to "collect nothing" rather than to a refund.
|
|
53
|
+
*/
|
|
54
|
+
export declare function amountDueNow(args: {
|
|
55
|
+
orderTotal: number;
|
|
56
|
+
pointsApplied: number;
|
|
57
|
+
/** `EmiScheduleResult.remainingBalance`, or 0 on a non-EMI order. */
|
|
58
|
+
emiRemainingBalance: number;
|
|
59
|
+
}): number;
|
|
60
|
+
/**
|
|
61
|
+
* The `emi*` fields written onto an order document.
|
|
62
|
+
*
|
|
63
|
+
* 🛑 One builder for both creation paths. These are eleven fields that have to
|
|
64
|
+
* be written identically by `createOrderForGroup` and
|
|
65
|
+
* `createPhonePeGroupOrder`, and the second one carried **none of them** —
|
|
66
|
+
* every order paid online lost its instalment plan entirely. Two hand-written
|
|
67
|
+
* copies is the create-vs-create axis of Root Cause #39, and these two
|
|
68
|
+
* functions have already drifted once before over add-on fees (#65).
|
|
69
|
+
*/
|
|
70
|
+
export declare function emiOrderFields(schedule: EmiScheduleResult | null, emiTenureMonths: number | undefined): {
|
|
71
|
+
emiEnabled?: true;
|
|
72
|
+
emiTenureMonths?: number;
|
|
73
|
+
emiTokenAmount?: number;
|
|
74
|
+
emiSurchargeAmount?: number;
|
|
75
|
+
emiSurchargeSellerShare?: number;
|
|
76
|
+
emiInstallments?: Array<{
|
|
77
|
+
index: number;
|
|
78
|
+
dueDate: Date;
|
|
79
|
+
amount: number;
|
|
80
|
+
status: "pending";
|
|
81
|
+
}>;
|
|
82
|
+
emiRemainingBalance?: number;
|
|
83
|
+
emiComplete?: false;
|
|
84
|
+
};
|