@mohasinac/appkit 4.59.0 → 4.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
- package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
- package/dist/_internal/server/features/brands/actions.js +78 -4
- package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
- package/dist/_internal/server/features/checkout/actions.js +668 -183
- package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
- package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
- package/dist/_internal/server/features/gateway-events/index.js +10 -0
- package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
- package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
- package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
- package/dist/_internal/server/features/gateway-events/redact.js +151 -0
- package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
- package/dist/_internal/server/features/grievance/actions.js +173 -0
- package/dist/_internal/server/features/grievance/index.d.ts +8 -0
- package/dist/_internal/server/features/grievance/index.js +8 -0
- package/dist/_internal/server/features/grouped/data.js +28 -3
- package/dist/_internal/server/features/lottery/actions.js +99 -4
- package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
- package/dist/_internal/server/features/order-completion/actions.js +336 -0
- package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
- package/dist/_internal/server/features/order-completion/index.js +7 -0
- package/dist/_internal/server/features/orders/actions.d.ts +13 -0
- package/dist/_internal/server/features/orders/actions.js +56 -2
- package/dist/_internal/server/features/orders/adapters.js +26 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
- package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
- package/dist/_internal/server/features/orders/index.d.ts +2 -1
- package/dist/_internal/server/features/orders/index.js +1 -1
- package/dist/_internal/server/features/orders/service.d.ts +60 -0
- package/dist/_internal/server/features/orders/service.js +74 -0
- package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
- package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
- package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
- package/dist/_internal/server/features/payment-cases/index.js +9 -0
- package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
- package/dist/_internal/server/features/payouts/actions.js +84 -10
- package/dist/_internal/server/features/points/adjust.d.ts +61 -0
- package/dist/_internal/server/features/points/adjust.js +121 -0
- package/dist/_internal/server/features/points/award.d.ts +65 -0
- package/dist/_internal/server/features/points/award.js +145 -0
- package/dist/_internal/server/features/price-index/market.d.ts +33 -0
- package/dist/_internal/server/features/price-index/market.js +54 -1
- package/dist/_internal/server/features/products/data.js +40 -22
- package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
- package/dist/_internal/server/features/protection-claims/actions.js +228 -0
- package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
- package/dist/_internal/server/features/protection-claims/index.js +8 -0
- package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
- package/dist/_internal/server/features/refunds/actions.js +259 -17
- package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
- package/dist/_internal/server/features/refunds/capacity.js +37 -0
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/search/index-build/sources.js +20 -1
- package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
- package/dist/_internal/server/features/shipping/reconcile.js +34 -3
- package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
- package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
- package/dist/_internal/server/features/stores/tabs.js +24 -4
- package/dist/_internal/server/functions/https.d.ts +31 -1
- package/dist/_internal/server/functions/https.js +52 -1
- package/dist/_internal/server/functions/scheduled.d.ts +72 -1
- package/dist/_internal/server/functions/scheduled.js +124 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
- package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
- package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
- package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
- package/dist/_internal/server/jobs/core/index.d.ts +2 -0
- package/dist/_internal/server/jobs/core/index.js +2 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
- package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
- package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
- package/dist/_internal/server/jobs/handlers/index.js +5 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
- package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +71 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
- package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
- package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
- package/dist/_internal/shared/checkout/lanes.js +46 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
- package/dist/_internal/shared/checkout/order-math.js +251 -3
- package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
- package/dist/_internal/shared/checkout/refundable-base.js +122 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
- package/dist/_internal/shared/features/auctions/config.js +30 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
- package/dist/_internal/shared/features/brands/schema.js +18 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
- package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
- package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
- package/dist/_internal/shared/features/print/label-profiles.js +350 -0
- package/dist/_internal/shared/features/products/to-product-item.js +33 -5
- package/dist/_internal/shared/fees/calculator.d.ts +31 -2
- package/dist/_internal/shared/fees/calculator.js +103 -5
- package/dist/_internal/shared/fees/india-states.d.ts +195 -0
- package/dist/_internal/shared/fees/india-states.js +190 -0
- package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
- package/dist/_internal/shared/fees/payout-hold.js +74 -0
- package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
- package/dist/_internal/shared/obligations/deadlines.js +155 -0
- package/dist/_internal/shared/shipping/quote.d.ts +96 -0
- package/dist/_internal/shared/shipping/quote.js +67 -0
- package/dist/_internal/shared/time/business-days.d.ts +91 -0
- package/dist/_internal/shared/time/business-days.js +185 -0
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +9 -4
- package/dist/client.js +26 -3
- package/dist/configs/tailwind.d.ts +0 -12
- package/dist/configs/tailwind.js +28 -15
- package/dist/constants/api-endpoints.d.ts +51 -9
- package/dist/constants/api-endpoints.js +23 -7
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +131 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/about/components/DeveloperView.js +5 -2
- package/dist/features/about/components/FAQPageView.js +2 -1
- package/dist/features/about/components/FeesView.js +2 -1
- package/dist/features/about/components/HelpPageView.js +2 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
- package/dist/features/about/components/HowEmiWorksView.js +2 -1
- package/dist/features/about/components/HowOffersWorkView.js +2 -1
- package/dist/features/about/components/HowOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowReviewsWorkView.js +2 -1
- package/dist/features/about/components/PolicyPageView.js +2 -1
- package/dist/features/about/components/ProseHero.d.ts +73 -0
- package/dist/features/about/components/ProseHero.js +18 -0
- package/dist/features/about/components/SecurityPrivacyView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +2 -1
- package/dist/features/about/components/TrackOrderView.js +2 -1
- package/dist/features/about/components/UnauthorizedView.js +1 -1
- package/dist/features/about/index.d.ts +2 -0
- package/dist/features/about/index.js +5 -0
- package/dist/features/account/actions/address-actions.d.ts +26 -2
- package/dist/features/account/actions/address-actions.js +25 -1
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOrdersView.js +17 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/hooks/useNotifications.js +29 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
- package/dist/features/admin/components/AdminBeysView.js +2 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCategoriesView.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
- package/dist/features/admin/components/AdminContactEditorView.js +1 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
- package/dist/features/admin/components/AdminFaqsView.js +2 -2
- package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
- package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
- package/dist/features/admin/components/AdminPartsView.js +2 -2
- package/dist/features/admin/components/AdminProductsView.js +75 -4
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
- package/dist/features/admin/components/AdminUsersView.js +72 -1
- package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -4
- package/dist/features/admin/components/DataListingView.d.ts +28 -0
- package/dist/features/admin/components/DataListingView.js +175 -14
- package/dist/features/admin/components/DataTable.d.ts +12 -1
- package/dist/features/admin/components/DataTable.js +3 -3
- package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/OwnerQuickActions.js +60 -2
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
- package/dist/features/admin/components/index.d.ts +4 -0
- package/dist/features/admin/components/index.js +6 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
- package/dist/features/admin/constants/filter-tabs.js +106 -0
- package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
- package/dist/features/admin/repository/site-settings.repository.js +82 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
- package/dist/features/admin/schemas/firestore.js +5 -1
- package/dist/features/admin/schemas/site-settings-form.js +30 -1
- package/dist/features/auctions/actions/bid-actions.js +13 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
- package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
- package/dist/features/audit-log/schemas/firestore.js +14 -0
- package/dist/features/auth/permissions/constants.d.ts +17 -1
- package/dist/features/auth/permissions/constants.js +18 -0
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
- package/dist/features/categories/api/[id]/description-template/route.js +107 -1
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
- package/dist/features/categories/components/BundleDetailView.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryForm.js +12 -1
- package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
- package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
- package/dist/features/categories/repository/market-price-index.repository.js +105 -0
- package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
- package/dist/features/categories/repository/price-observation.repository.js +132 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -1
- package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
- package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
- package/dist/features/faq/components/RelatedFAQs.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
- package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
- package/dist/features/gateway-events/schemas/firestore.js +125 -0
- package/dist/features/gateway-events/schemas/index.d.ts +81 -0
- package/dist/features/gateway-events/schemas/index.js +79 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.js +37 -1
- package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
- package/dist/features/homepage/components/HeroCarousel.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
- package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
- package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
- package/dist/features/inventory/components/ReceiveStationView.js +413 -0
- package/dist/features/inventory/components/operator-message.d.ts +29 -0
- package/dist/features/inventory/components/operator-message.js +41 -0
- package/dist/features/inventory/index.d.ts +13 -0
- package/dist/features/inventory/index.js +28 -0
- package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
- package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
- package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
- package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
- package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
- package/dist/features/inventory/schemas/firestore.d.ts +197 -0
- package/dist/features/inventory/schemas/firestore.js +159 -0
- package/dist/features/layout/AppLayoutShell.d.ts +12 -1
- package/dist/features/layout/AppLayoutShell.js +25 -12
- package/dist/features/layout/AutoBreadcrumbs.js +162 -3
- package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
- package/dist/features/layout/BottomNavbar.d.ts +1 -1
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/media/MediaAvatar.js +1 -1
- package/dist/features/media/MediaImage.d.ts +24 -4
- package/dist/features/media/MediaImage.js +44 -4
- package/dist/features/media/MediaLightbox.js +1 -1
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +91 -3
- package/dist/features/obligations/constants/policies.d.ts +92 -0
- package/dist/features/obligations/constants/policies.js +378 -0
- package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
- package/dist/features/obligations/repository/obligations.repository.js +158 -0
- package/dist/features/obligations/schemas/firestore.d.ts +160 -0
- package/dist/features/obligations/schemas/firestore.js +87 -0
- package/dist/features/orders/actions/order-actions.d.ts +43 -4
- package/dist/features/orders/actions/order-actions.js +60 -3
- package/dist/features/orders/actions/refund-actions.js +70 -10
- package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
- package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +3 -1
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +6 -0
- package/dist/features/orders/constants/transitions.d.ts +59 -0
- package/dist/features/orders/constants/transitions.js +104 -0
- package/dist/features/orders/repository/orders.repository.d.ts +109 -6
- package/dist/features/orders/repository/orders.repository.js +464 -21
- package/dist/features/orders/schemas/firestore.d.ts +270 -0
- package/dist/features/orders/schemas/firestore.js +24 -0
- package/dist/features/orders/schemas/index.d.ts +61 -0
- package/dist/features/orders/schemas/index.js +22 -0
- package/dist/features/orders/types/index.d.ts +79 -0
- package/dist/features/parts/components/PartsIndexPageView.js +8 -1
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
- package/dist/features/payment-cases/schemas/firestore.js +105 -0
- package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
- package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
- package/dist/features/payments/repository/payout.repository.d.ts +0 -1
- package/dist/features/payments/repository/payout.repository.js +0 -1
- package/dist/features/points/repository/points.repository.d.ts +122 -0
- package/dist/features/points/repository/points.repository.js +304 -1
- package/dist/features/points/schemas/adjust-form.d.ts +58 -0
- package/dist/features/points/schemas/adjust-form.js +63 -0
- package/dist/features/points/schemas/firestore.d.ts +108 -1
- package/dist/features/points/schemas/firestore.js +84 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
- package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
- package/dist/features/price-index/market-stats.d.ts +82 -0
- package/dist/features/price-index/market-stats.js +230 -0
- package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
- package/dist/features/print/components/BoxLabelPrintView.js +66 -0
- package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
- package/dist/features/print/components/LabelProfilePicker.js +38 -0
- package/dist/features/print/components/labels.d.ts +117 -0
- package/dist/features/print/components/labels.js +258 -0
- package/dist/features/print/components/labels.style.css +46 -0
- package/dist/features/print/index.d.ts +6 -0
- package/dist/features/print/index.js +16 -0
- package/dist/features/products/columns/productTableColumns.js +5 -4
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +34 -23
- package/dist/features/products/components/ProductGrid.js +86 -62
- package/dist/features/products/constants/card-badges.d.ts +97 -0
- package/dist/features/products/constants/card-badges.js +100 -0
- package/dist/features/products/repository/products.repository.js +34 -1
- package/dist/features/products/schemas/firestore.d.ts +46 -3
- package/dist/features/products/schemas/firestore.js +21 -0
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
- package/dist/features/promotions/schemas/firestore.js +30 -3
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
- package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
- package/dist/features/protection-claims/schemas/firestore.js +130 -0
- package/dist/features/reviews/actions/review-actions.js +3 -2
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/schemas/firestore.d.ts +20 -1
- package/dist/features/scams/constants/scam-types.d.ts +2 -0
- package/dist/features/scams/constants/scam-types.js +18 -0
- package/dist/features/scams/schemas/report-form.d.ts +3 -3
- package/dist/features/scams/schemas/report-form.js +26 -1
- package/dist/features/search/components/Search.js +9 -9
- package/dist/features/search/components/SearchResultsClient.js +18 -3
- package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/seller/actions/offer-actions.js +16 -1
- package/dist/features/seller/actions/seller-actions.js +14 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
- package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
- package/dist/features/seller/components/PrintCenterView.js +41 -30
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
- package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
- package/dist/features/seller/components/SellerProductShell.js +123 -13
- package/dist/features/seller/components/SellerReviewsView.js +4 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
- package/dist/features/seller/schemas/coupon-form.js +27 -0
- package/dist/features/seller/schemas/product-form.d.ts +3 -0
- package/dist/features/seller/schemas/product-form.js +5 -0
- package/dist/features/shell/FormShell.js +2 -2
- package/dist/features/shell/QuickFormDrawer.js +14 -0
- package/dist/features/shell/SectionForm.d.ts +19 -0
- package/dist/features/shell/SectionForm.js +133 -9
- package/dist/features/shell/SectionForm.style.css +54 -0
- package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
- package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
- package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/support/schemas/firestore.d.ts +73 -2
- package/dist/features/support/schemas/firestore.js +59 -0
- package/dist/index.d.ts +16 -24
- package/dist/index.js +38 -47
- package/dist/next/ErrorBoundary.js +1 -1
- package/dist/next/routing/route-map.d.ts +24 -0
- package/dist/next/routing/route-map.js +40 -0
- package/dist/providers/payment-manual/index.d.ts +15 -1
- package/dist/providers/payment-manual/index.js +37 -0
- package/dist/providers/payment-phonepe/index.d.ts +14 -1
- package/dist/providers/payment-phonepe/index.js +36 -0
- package/dist/providers/payment-razorpay/index.d.ts +44 -1
- package/dist/providers/payment-razorpay/index.js +99 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
- package/dist/providers/shipping-shiprocket/index.js +18 -0
- package/dist/schemas/registry.d.ts +44 -11
- package/dist/server-entry.d.ts +2 -0
- package/dist/server-entry.js +27 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +93 -0
- package/dist/styles.css +418 -149
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +15 -12
- package/dist/tokens/themes/default-light.js +14 -11
- package/dist/tokens/tokens.css +106 -23
- package/dist/ui/DataTable.style.css +4 -4
- package/dist/ui/columns/cell-renderers.d.ts +20 -0
- package/dist/ui/columns/cell-renderers.js +26 -1
- package/dist/ui/columns/index.d.ts +1 -1
- package/dist/ui/columns/index.js +4 -1
- package/dist/ui/components/Badge.style.css +1 -1
- package/dist/ui/components/Breadcrumb.js +19 -1
- package/dist/ui/components/BulkActionBar.style.css +4 -4
- package/dist/ui/components/Button.style.css +1 -1
- package/dist/ui/components/Checkbox.style.css +1 -1
- package/dist/ui/components/FallbackShell.js +1 -1
- package/dist/ui/components/Form.d.ts +14 -1
- package/dist/ui/components/Form.js +2 -2
- package/dist/ui/components/FormField.js +18 -4
- package/dist/ui/components/FormField.style.css +43 -3
- package/dist/ui/components/GatedPrice.style.css +1 -1
- package/dist/ui/components/IconButton.style.css +1 -1
- package/dist/ui/components/Input.js +22 -1
- package/dist/ui/components/Input.style.css +38 -22
- package/dist/ui/components/ListingFilterDrawer.js +1 -1
- package/dist/ui/components/ListingToolbar.js +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/Radio.style.css +1 -1
- package/dist/ui/components/Repeater.d.ts +53 -0
- package/dist/ui/components/Repeater.js +7 -0
- package/dist/ui/components/RichTextEditor.d.ts +20 -1
- package/dist/ui/components/RichTextEditor.js +23 -2
- package/dist/ui/components/Select.style.css +7 -21
- package/dist/ui/components/Semantic.js +27 -3
- package/dist/ui/components/TagInput.style.css +4 -9
- package/dist/ui/components/Textarea.style.css +4 -18
- package/dist/ui/components/Toggle.style.css +1 -1
- package/dist/ui/components/Typography.d.ts +3 -3
- package/dist/ui/components/Typography.js +15 -15
- package/dist/ui/components/ViewToggle.style.css +4 -4
- package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
- package/dist/ui/forms/AddAnotherToggle.js +56 -0
- package/dist/ui/forms/ColorPickerField.js +15 -2
- package/dist/ui/forms/FieldCheckbox.js +15 -2
- package/dist/ui/forms/FieldInput.d.ts +22 -1
- package/dist/ui/forms/FieldInput.js +19 -5
- package/dist/ui/forms/FieldLabel.d.ts +35 -0
- package/dist/ui/forms/FieldLabel.js +70 -0
- package/dist/ui/forms/FieldSelect.d.ts +4 -1
- package/dist/ui/forms/FieldSelect.js +27 -5
- package/dist/ui/forms/FieldTextarea.d.ts +4 -1
- package/dist/ui/forms/FieldTextarea.js +22 -5
- package/dist/ui/forms/FormShell.d.ts +17 -1
- package/dist/ui/forms/FormShell.js +36 -2
- package/dist/ui/forms/FormShell.style.css +41 -33
- package/dist/ui/forms/index.d.ts +5 -1
- package/dist/ui/forms/index.js +2 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/utils/id-generators.d.ts +80 -0
- package/dist/utils/id-generators.js +93 -0
- package/dist/utils/search-txt-builders.d.ts +12 -0
- package/dist/utils/search-txt-builders.js +17 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +106 -5
- package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
- package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
- package/dist/features/loyalty/api/balance/route.d.ts +0 -12
- package/dist/features/loyalty/api/balance/route.js +0 -41
- package/dist/features/loyalty/api/earn/route.d.ts +0 -12
- package/dist/features/loyalty/api/earn/route.js +0 -38
- package/dist/features/loyalty/api/history/route.d.ts +0 -12
- package/dist/features/loyalty/api/history/route.js +0 -58
- package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
- package/dist/features/loyalty/api/redeem/route.js +0 -38
- package/dist/features/loyalty/columns/index.d.ts +0 -17
- package/dist/features/loyalty/columns/index.js +0 -40
- package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
- package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
- package/dist/features/loyalty/components/index.d.ts +0 -1
- package/dist/features/loyalty/components/index.js +0 -1
- package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
- package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
- package/dist/features/loyalty/index.d.ts +0 -6
- package/dist/features/loyalty/index.js +0 -6
- package/dist/features/loyalty/manifest.d.ts +0 -2
- package/dist/features/loyalty/manifest.js +0 -16
- package/dist/features/loyalty/messages/en.json +0 -19
- package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
- package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
- package/dist/features/loyalty/schemas/index.d.ts +0 -85
- package/dist/features/loyalty/schemas/index.js +0 -40
- package/dist/features/loyalty/server.d.ts +0 -6
- package/dist/features/loyalty/server.js +0 -6
- package/dist/features/loyalty/types/index.d.ts +0 -31
- package/dist/features/loyalty/types/index.js +0 -1
- package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
- package/dist/features/loyalty/types/loyalty-config.js +0 -1
- package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
- package/dist/features/loyalty/types/loyalty-math.js +0 -27
- package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
- package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
|
@@ -50,6 +50,23 @@ const ORDER_LANE_TABS = [
|
|
|
50
50
|
{ id: "standard", label: "Normal" },
|
|
51
51
|
{ id: "auction", label: "Auction wins" },
|
|
52
52
|
{ id: "offer", label: "Offer wins" },
|
|
53
|
+
/*
|
|
54
|
+
* 🛑 `"preorder"` WITHOUT A HYPHEN, and this is not a typo to tidy up.
|
|
55
|
+
*
|
|
56
|
+
* `OrderType` (`orders/utils/order-splitter.ts`) spells it `"preorder"`,
|
|
57
|
+
* while `ListingType` spells the same concept `"pre-order"` WITH a hyphen.
|
|
58
|
+
* Two unions, two spellings, and the tab must match the one actually stored
|
|
59
|
+
* on `OrderDocument.orderType` — a tab id matching no stored value returns
|
|
60
|
+
* zero rows forever with no error anywhere (Root Cause #33), which is
|
|
61
|
+
* exactly the failure this file's own comment above warns about.
|
|
62
|
+
*
|
|
63
|
+
* These two lanes existed in the union and had no tab, so pre-order and
|
|
64
|
+
* prize-draw orders were reachable only by landing on "All" and scrolling.
|
|
65
|
+
* Five lanes, four tabs — the eighth instance this session of a
|
|
66
|
+
* hand-written enumeration drifting from the union it filters.
|
|
67
|
+
*/
|
|
68
|
+
{ id: "preorder", label: "Pre-orders" },
|
|
69
|
+
{ id: "prize-draw", label: "Prize draws" },
|
|
53
70
|
];
|
|
54
71
|
/**
|
|
55
72
|
* Payment-method tabs — a THIRD axis, independent of the lane tabs above and
|
|
@@ -97,7 +97,7 @@ export function UserSidebar({ items, groups, mobileOpen = false, onCloseMobile,
|
|
|
97
97
|
// ── Persistent sidebar variant (desktop left slide-over, mobile bottom-sheet) ──────
|
|
98
98
|
if (variant === "sidebar") {
|
|
99
99
|
const handleToggle = onToggle ?? close;
|
|
100
|
-
return (_jsxs(_Fragment, { children: [
|
|
100
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: `hidden lg:flex fixed ${rail.edgeClass} z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[13rem] ${desktopOpen ? "translate-x-0" : rail.collapsedTranslateClass}`, children: [_jsxs(Stack, { border: "default", surface: "default", className: `flex-1 ${rail.borderClass} dark:border-[var(--appkit-color-border)] ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: _jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: "My Account" }) }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: _jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname }) }), renderFooter && _jsx(Div, { border: "top", padding: "inline", children: renderFooter() })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle, edge: hand })] }), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "My Account", children: _jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname, onItemClick: close }) }) })] }));
|
|
101
101
|
}
|
|
102
102
|
// ── Overlay variant (default) — portal on desktop, bottom-sheet on mobile ─
|
|
103
103
|
const content = (_jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname, onItemClick: close }));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { useOptionalSession } from "../../../react/contexts/SessionContext.js";
|
|
2
3
|
import { apiClient } from "../../../http/index.js";
|
|
3
4
|
import { NOTIFICATIONS_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
4
5
|
/**
|
|
@@ -10,7 +11,35 @@ export function useNotifications(limit = 10, options) {
|
|
|
10
11
|
const notificationsEndpoint = options?.notificationsEndpoint ?? NOTIFICATIONS_ENDPOINTS.LIST;
|
|
11
12
|
const readAllEndpoint = options?.readAllEndpoint ?? NOTIFICATIONS_ENDPOINTS.READ_ALL;
|
|
12
13
|
const readEndpoint = options?.readEndpoint ?? NOTIFICATIONS_ENDPOINTS.BY_ID;
|
|
14
|
+
/*
|
|
15
|
+
* 🛑 SIGNED-OUT VISITORS WERE POLLING AN AUTH-ONLY ENDPOINT.
|
|
16
|
+
*
|
|
17
|
+
* The interval below was tuned carefully and at length — and the query had
|
|
18
|
+
* no `enabled` gate at all. `TitleBar` mounts this hook on every page, so a
|
|
19
|
+
* signed-out visitor fetched `/api/notifications` on mount and again every
|
|
20
|
+
* five minutes per open tab, collecting a 401 each time. That is the
|
|
21
|
+
* audit's "two 401s fire for signed-out visitors, including a notifications
|
|
22
|
+
* poll that should not run at all", and signed-out is the majority of
|
|
23
|
+
* traffic on a public catalogue.
|
|
24
|
+
*
|
|
25
|
+
* Every one of those was a billed function invocation (Rule #6) spent
|
|
26
|
+
* asking for the unread count of nobody.
|
|
27
|
+
*
|
|
28
|
+
* `useOptionalSession`, not `useSession`: this hook is library code mounted
|
|
29
|
+
* in shared chrome, so it must not require a provider — and with no
|
|
30
|
+
* provider it fails CLOSED, which is the safe direction for a request that
|
|
31
|
+
* can only 401. Same precedent as `useCanSeePrices`.
|
|
32
|
+
*
|
|
33
|
+
* `loading` is deliberately part of the gate. `SessionProvider` mounts
|
|
34
|
+
* `initialUser={null}` (the root layout must stay static), so `user` is
|
|
35
|
+
* null on every hard load until Firebase resolves — firing then would 401
|
|
36
|
+
* for a signed-IN user too, and firing again afterwards would double the
|
|
37
|
+
* request.
|
|
38
|
+
*/
|
|
39
|
+
const session = useOptionalSession();
|
|
40
|
+
const isSignedIn = Boolean(session?.user?.uid) && !session?.loading;
|
|
13
41
|
const { data, isLoading, refetch } = useQuery({
|
|
42
|
+
enabled: isSignedIn,
|
|
14
43
|
queryKey: ["notifications", "list", String(limit)],
|
|
15
44
|
queryFn: () => apiClient.get(`${notificationsEndpoint}?limit=${limit}`),
|
|
16
45
|
staleTime: NOTIFICATIONS_POLL_MS,
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { couponsRepository } from "../../promotions/repository/coupons.repository.js";
|
|
8
8
|
import { NotFoundError, ValidationError } from "../../../errors/index.js";
|
|
9
9
|
import { serverLogger } from "../../../monitoring/index.js";
|
|
10
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
11
|
+
import { claimedCouponsRepository } from "../../promotions/repository/claimed-coupons.repository.js";
|
|
10
12
|
import { recordAdminAction } from "../../../_internal/server/features/audit-log/actions.js";
|
|
11
13
|
import { AdminAuditActionValues } from "../../audit-log/schemas/firestore.js";
|
|
12
14
|
export async function adminCreateCoupon(adminId, input) {
|
|
@@ -58,6 +60,40 @@ export async function adminUpdateCoupon(adminId, id, input) {
|
|
|
58
60
|
: {}),
|
|
59
61
|
};
|
|
60
62
|
const updated = await couponsRepository.update(id, updateData);
|
|
63
|
+
/*
|
|
64
|
+
* D59 — re-sync every wallet's denormalised snapshot. Nothing did this, so
|
|
65
|
+
* `/user/coupons` kept rendering the terms as they were at CLAIM time while
|
|
66
|
+
* checkout applied the live ones.
|
|
67
|
+
*
|
|
68
|
+
* 🛑 Display only, verified: redemption reads the live coupon via
|
|
69
|
+
* `getCouponByCode`, so the drift never changed what was charged. Fixing it
|
|
70
|
+
* stops the wallet advertising terms checkout will not honour.
|
|
71
|
+
*
|
|
72
|
+
* Best-effort and fire-and-forget: the admin's edit has already committed,
|
|
73
|
+
* and a failed re-sync must not fail it. The fallback is a stale display —
|
|
74
|
+
* which is exactly today's behaviour, so the worst case is no regression.
|
|
75
|
+
*/
|
|
76
|
+
void claimedCouponsRepository
|
|
77
|
+
.resyncSnapshotsForCoupon(id, {
|
|
78
|
+
name: updated.name,
|
|
79
|
+
...(updated.description ? { description: updated.description } : {}),
|
|
80
|
+
type: updated.type,
|
|
81
|
+
scope: updated.scope ?? "admin",
|
|
82
|
+
...(updated.storeId ? { storeId: updated.storeId } : {}),
|
|
83
|
+
discount: updated.discount,
|
|
84
|
+
restrictions: updated.restrictions,
|
|
85
|
+
})
|
|
86
|
+
.then((n) => {
|
|
87
|
+
if (n > 0)
|
|
88
|
+
serverLogger.info("adminUpdateCoupon: wallet snapshots re-synced", { couponId: id, wallets: n });
|
|
89
|
+
if (n === 500) {
|
|
90
|
+
serverLogger.warn("adminUpdateCoupon: snapshot re-sync hit its 500-wallet bound — later wallets still show stale terms", { couponId: id });
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.catch((err) => {
|
|
94
|
+
void normalizeError(err);
|
|
95
|
+
serverLogger.warn("adminUpdateCoupon: wallet snapshot re-sync failed (display only)", { couponId: id });
|
|
96
|
+
});
|
|
61
97
|
serverLogger.info("adminUpdateCoupon", {
|
|
62
98
|
adminId,
|
|
63
99
|
couponId: id,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* AdminBeysView — browse and correct the generated Beyblade database.
|
|
5
5
|
*
|
|
@@ -174,5 +174,5 @@ export function AdminBeysView({ children, ...props }) {
|
|
|
174
174
|
if (React.Children.count(children) > 0) {
|
|
175
175
|
return (_jsxs(ListingLayout, { portal: "admin", ...props, children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Beyblades" }), children] }));
|
|
176
176
|
}
|
|
177
|
-
return
|
|
177
|
+
return _jsx(DataListingView, { config: ADMIN_BEYS_CONFIG });
|
|
178
178
|
}
|
|
@@ -207,7 +207,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
207
207
|
}, placeholder: "e.g. How to Grade Pok\u00E9mon Cards" }), _jsx(Input, { label: "Slug", value: values.slug, onChange: (e) => {
|
|
208
208
|
setSlugManual(true);
|
|
209
209
|
onChange({ slug: e.target.value });
|
|
210
|
-
}, placeholder: "blog-how-to-grade-pokemon-cards", helperText: "Auto-generated from title. Must start with 'blog-'." }), _jsx(Input, { label: "Excerpt", value: values.excerpt, onChange: (e) => onChange({ excerpt: e.target.value }), placeholder: "Short summary shown in listings and cards" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Content" }), _jsx(RichTextEditor, { value: values.content, onChange: (v) => onChange({ content: v }), placeholder: "Write your article here...", minHeightClassName: "min-h-[320px]" })] })] })),
|
|
210
|
+
}, placeholder: "blog-how-to-grade-pokemon-cards", helperText: "Auto-generated from title. Must start with 'blog-'." }), _jsx(Input, { label: "Excerpt", value: values.excerpt, onChange: (e) => onChange({ excerpt: e.target.value }), placeholder: "Short summary shown in listings and cards" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Content" }), _jsx(RichTextEditor, { ariaLabel: "Content", value: values.content, onChange: (v) => onChange({ content: v }), placeholder: "Write your article here...", minHeightClassName: "min-h-[320px]" })] })] })),
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
213
|
id: "media",
|
|
@@ -265,7 +265,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
265
265
|
if (embedded) {
|
|
266
266
|
return _jsx(Div, { className: `${__O.yAuto} ${__P.p4}`, children: formContent });
|
|
267
267
|
}
|
|
268
|
-
const previewSection = (_jsxs(Div, { rounded: "lg", padding: "5", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-raised)] max-h-[calc(100vh-12rem)] overflow-y-auto", children: [_jsx(Row, { textSize: "xs", textWeight: "semibold", color: "muted", className: "mb-3 uppercase tracking-wide", align: "center", gap: "sm", children: _jsx(Span, { children: "Live preview" }) }), draft.coverImage ? (_jsx(Div, { className: "relative h-64 w-full mb-4", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: draft.coverImage, alt: "", size: "hero" }) })) : null, _jsx(Heading, { level: 1, className: "mb-1", size: "2xl", weight: "bold", children: draft.title || "Untitled post" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mb-4", size: "xs", children: [draft.authorName || "Anonymous", draft.category ? ` · ${draft.category}` : ""] }), draft.excerpt ? (_jsx(Text, { className: "italic text-[var(--appkit-color-text-
|
|
268
|
+
const previewSection = (_jsxs(Div, { rounded: "lg", padding: "5", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-raised)] max-h-[calc(100vh-12rem)] overflow-y-auto", children: [_jsx(Row, { textSize: "xs", textWeight: "semibold", color: "muted", className: "mb-3 uppercase tracking-wide", align: "center", gap: "sm", children: _jsx(Span, { children: "Live preview" }) }), draft.coverImage ? (_jsx(Div, { className: "relative h-64 w-full mb-4", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: draft.coverImage, alt: "", size: "hero" }) })) : null, _jsx(Heading, { level: 1, className: "mb-1", size: "2xl", weight: "bold", children: draft.title || "Untitled post" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mb-4", size: "xs", children: [draft.authorName || "Anonymous", draft.category ? ` · ${draft.category}` : ""] }), draft.excerpt ? (_jsx(Text, { className: "italic text-[var(--appkit-color-text-muted)] mb-4", children: draft.excerpt })) : null, _jsx(RichTextRenderer, { html: draft.content || "<em>No content yet…</em>" }), draft.tags.length > 0 ? (_jsx(Row, { wrap: true, gap: "xs", className: "mt-6", children: draft.tags.map((t) => (_jsxs(Span, { size: "xs", className: "bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)]", rounded: "full", padding: "pill-xs", children: ["#", t] }, t))) })) : null] }, "preview"));
|
|
269
269
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Post" : "New Blog Post", sections: [
|
|
270
270
|
_jsxs(Div, { className: "lg:grid lg:grid-cols-[3fr_2fr] lg:gap-[1.5rem]", children: [_jsx(Div, { children: formContent }), _jsx(Div, { className: "mt-6 lg:mt-0 lg:sticky lg:top-4 lg:self-start", children: previewSection })] }, "split"),
|
|
271
271
|
] }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* Create or edit a brand.
|
|
5
5
|
*
|
|
@@ -20,7 +20,8 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
20
20
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
21
21
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
22
22
|
import { apiClient } from "../../../http/index.js";
|
|
23
|
-
import { ConfirmDeleteModal, Div, StackedViewShell, useToast } from "../../../ui/index.js";
|
|
23
|
+
import { ConfirmDeleteModal, Div, Input, StackedViewShell, Textarea, useToast } from "../../../ui/index.js";
|
|
24
|
+
import { Repeater } from "../../../ui/components/Repeater.js";
|
|
24
25
|
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues.js";
|
|
25
26
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
|
|
26
27
|
import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell.js";
|
|
@@ -41,6 +42,8 @@ const EMPTY = {
|
|
|
41
42
|
founded: "",
|
|
42
43
|
isActive: true,
|
|
43
44
|
displayOrder: "",
|
|
45
|
+
highlightsText: "",
|
|
46
|
+
faqs: [],
|
|
44
47
|
};
|
|
45
48
|
function toBrandSlug(str) {
|
|
46
49
|
const base = str
|
|
@@ -82,6 +85,8 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
|
|
|
82
85
|
founded: brand.founded !== undefined ? String(brand.founded) : "",
|
|
83
86
|
isActive: brand.isActive ?? true,
|
|
84
87
|
displayOrder: brand.displayOrder !== undefined ? String(brand.displayOrder) : "",
|
|
88
|
+
highlightsText: (brand.highlights ?? []).join("\n"),
|
|
89
|
+
faqs: brand.faqs ?? [],
|
|
85
90
|
});
|
|
86
91
|
}, [brandQuery.data]);
|
|
87
92
|
const sections = React.useMemo(() => buildSectionsFromSchema(brandFormSchema, {
|
|
@@ -90,6 +95,16 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
|
|
|
90
95
|
type: "brand-logo",
|
|
91
96
|
brand: values.name || values.slug,
|
|
92
97
|
}), onChange: (url) => onChange({ logoURL: url }) })),
|
|
98
|
+
/*
|
|
99
|
+
* The FAQ rows. `Repeater` is the one promoted out of
|
|
100
|
+
* `AdminCategoryEditorView` on the condition its own author set —
|
|
101
|
+
* "promote it when a fourth caller appears outside this file" — so
|
|
102
|
+
* this is that caller rather than a second implementation.
|
|
103
|
+
*/
|
|
104
|
+
faqs: ({ values, onChange }) => {
|
|
105
|
+
const rows = values.faqs ?? [];
|
|
106
|
+
return (_jsx(Repeater, { rows: rows, onChange: (next) => onChange({ faqs: next }), blank: () => ({ question: "", answer: "" }), addLabel: "Add a question", max: 8, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Question", value: row.question, onChange: (e) => set({ question: e.target.value }), placeholder: "Are Takara-Tomy releases region-locked?" }), _jsx(Textarea, { label: "Answer", rows: 3, value: row.answer, onChange: (e) => set({ answer: e.target.value }) })] })) }));
|
|
107
|
+
},
|
|
93
108
|
},
|
|
94
109
|
}), [upload]);
|
|
95
110
|
const nav = useSectionFormNav(sections, form, { scope: "admin:brand-editor" });
|
|
@@ -112,6 +127,21 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
|
|
|
112
127
|
founded: optionalNumber(form.founded),
|
|
113
128
|
isActive: form.isActive,
|
|
114
129
|
displayOrder: optionalNumber(form.displayOrder),
|
|
130
|
+
/*
|
|
131
|
+
* 🛑 Sent even when EMPTY, which is what makes "remove the last
|
|
132
|
+
* highlight" work. The mapper tests `!== undefined`, so an empty
|
|
133
|
+
* array clears the list; omitting the key instead would leave the
|
|
134
|
+
* stored value untouched and the admin unable to delete what they
|
|
135
|
+
* can see.
|
|
136
|
+
*
|
|
137
|
+
* Blank lines and whitespace-only rows are dropped rather than
|
|
138
|
+
* stored — an empty bullet renders as a gap on the brand page.
|
|
139
|
+
*/
|
|
140
|
+
highlights: form.highlightsText
|
|
141
|
+
.split("\n")
|
|
142
|
+
.map((l) => l.trim())
|
|
143
|
+
.filter((l) => l.length > 0),
|
|
144
|
+
faqs: form.faqs.filter((f) => f.question.trim() && f.answer.trim()),
|
|
115
145
|
};
|
|
116
146
|
if (isEdit)
|
|
117
147
|
return apiClient.put(ADMIN_ENDPOINTS.BRAND_BY_ID(brandId), payload);
|
|
@@ -134,7 +134,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope = "a
|
|
|
134
134
|
* would have written that blank over a live bundle.
|
|
135
135
|
*
|
|
136
136
|
* Verified in production 2026-10-02: /admin/bundles/
|
|
137
|
-
* bundle-
|
|
137
|
+
* bundle-x-bx53-complete/edit polled for 14s with
|
|
138
138
|
* `input[name="name"]` and `input[name="priceRupees"]` both `""` and no
|
|
139
139
|
* member count, while `GET /api/admin/bundles/{id}` returned 200 with the
|
|
140
140
|
* real bundle (`{ success, data: <bundle> }`, no nested `data`). I did not
|
|
@@ -76,7 +76,7 @@ function ZonePicker({ selected, onChange, disabled, }) {
|
|
|
76
76
|
// ── Background Editor ────────────────────────────────────────────────────────
|
|
77
77
|
function BackgroundEditor({ value, onChange, }) {
|
|
78
78
|
const set = (patch) => onChange({ ...value, ...patch });
|
|
79
|
-
return (_jsxs(Stack, { gap: "3", children: [_jsx(Select, { label: "Background type", value: value.type, onChange: (e) => onChange({ type: e.target.value }), options: BG_TYPE_OPTIONS }), (value.type === "image" || value.type === "video") && (_jsxs(_Fragment, { children: [_jsx(Input, { label: `${value.type === "image" ? "Image" : "Video"} URL`, value: value.url ?? "", onChange: (e) => set({ url: e.target.value }), placeholder: "https://..." }), _jsx(Input, { label: "Mobile URL (optional)", value: value.mobileUrl ?? "", onChange: (e) => set({ mobileUrl: e.target.value || undefined }), placeholder: "https://... (portrait crop)" }), value.type === "video" && (_jsx(Input, { label: "Poster / thumbnail URL", value: value.thumbnail ?? "", onChange: (e) => set({ thumbnail: e.target.value || undefined }), placeholder: "https://..." })), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "lg", border: "default", children: [_jsx(Toggle, { label: "Dim overlay", checked: value.dimOverlay?.enabled ?? false, onChange: (v) => set({ dimOverlay: { enabled: v, opacity: value.dimOverlay?.opacity ?? 40 } }) }), value.dimOverlay?.enabled && (_jsxs(Div, { children: [_jsxs(Text, { className: "mb-1", size: "sm", weight: "medium", children: ["Opacity (", value.dimOverlay.opacity, "%)"] }), _jsx(Slider, { min: 0, max: 90, step: 5, value: value.dimOverlay.opacity, onChange: (opacity) => set({ dimOverlay: { enabled: true, opacity } }), showValue: false })] }))] })] })), value.type === "color" && (_jsx(Input, { label: "Colour (CSS token or hex)", value: value.color ?? "", onChange: (e) => set({ color: e.target.value }), placeholder: "var(--appkit-color-primary) or #1a1a2e" })), value.type === "gradient" && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "From colour", value: value.gradientFrom ?? "", onChange: (e) => set({ gradientFrom: e.target.value }), placeholder: "var(--appkit-color-primary)" }), _jsx(Input, { label: "To colour", value: value.gradientTo ?? "", onChange: (e) => set({ gradientTo: e.target.value }), placeholder: "var(--appkit-color-secondary)" }), _jsx(Input, { label: "Angle (degrees)", type: "number", value: String(value.gradientAngle ?? 135), onChange: (e) => set({ gradientAngle: Number(e.target.value) }), min: 0, max: 360 })] }))] }));
|
|
79
|
+
return (_jsxs(Stack, { gap: "3", children: [_jsx(Select, { label: "Background type", value: value.type, onChange: (e) => onChange({ type: e.target.value }), options: BG_TYPE_OPTIONS }), (value.type === "image" || value.type === "video") && (_jsxs(_Fragment, { children: [_jsx(Input, { label: `${value.type === "image" ? "Image" : "Video"} URL`, value: value.url ?? "", onChange: (e) => set({ url: e.target.value }), placeholder: "https://..." }), _jsx(Input, { label: "Mobile URL (optional)", value: value.mobileUrl ?? "", onChange: (e) => set({ mobileUrl: e.target.value || undefined }), placeholder: "https://... (portrait crop)" }), value.type === "video" && (_jsx(Input, { label: "Poster / thumbnail URL", value: value.thumbnail ?? "", onChange: (e) => set({ thumbnail: e.target.value || undefined }), placeholder: "https://..." })), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "lg", border: "default", children: [_jsx(Toggle, { label: "Dim overlay", checked: value.dimOverlay?.enabled ?? false, onChange: (v) => set({ dimOverlay: { enabled: v, opacity: value.dimOverlay?.opacity ?? 40 } }) }), value.dimOverlay?.enabled && (_jsxs(Div, { children: [_jsxs(Text, { className: "mb-1", size: "sm", weight: "medium", children: ["Opacity (", value.dimOverlay.opacity, "%)"] }), _jsx(Slider, { "aria-label": `Dim overlay opacity, ${value.dimOverlay.opacity}%`, min: 0, max: 90, step: 5, value: value.dimOverlay.opacity, onChange: (opacity) => set({ dimOverlay: { enabled: true, opacity } }), showValue: false })] }))] })] })), value.type === "color" && (_jsx(Input, { label: "Colour (CSS token or hex)", value: value.color ?? "", onChange: (e) => set({ color: e.target.value }), placeholder: "var(--appkit-color-primary) or #1a1a2e" })), value.type === "gradient" && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "From colour", value: value.gradientFrom ?? "", onChange: (e) => set({ gradientFrom: e.target.value }), placeholder: "var(--appkit-color-primary)" }), _jsx(Input, { label: "To colour", value: value.gradientTo ?? "", onChange: (e) => set({ gradientTo: e.target.value }), placeholder: "var(--appkit-color-secondary)" }), _jsx(Input, { label: "Angle (degrees)", type: "number", value: String(value.gradientAngle ?? 135), onChange: (e) => set({ gradientAngle: Number(e.target.value) }), min: 0, max: 360 })] }))] }));
|
|
80
80
|
}
|
|
81
81
|
// ── Card Editor ──────────────────────────────────────────────────────────────
|
|
82
82
|
function CardEditor({ card, index, otherZones, onChange, onRemove, }) {
|
|
@@ -152,5 +152,5 @@ export function AdminCategoriesView({ children, ...props }) {
|
|
|
152
152
|
if (React.Children.count(children) > 0) {
|
|
153
153
|
return (_jsxs(ListingLayout, { portal: "admin", ...props, children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Categories" }), children] }));
|
|
154
154
|
}
|
|
155
|
-
return
|
|
155
|
+
return _jsx(DataListingView, { config: ADMIN_CATEGORIES_CONFIG });
|
|
156
156
|
}
|
|
@@ -4,11 +4,14 @@ import { useApiMutation } from "@mohasinac/appkit/client";
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
import { useQuery } from "@tanstack/react-query";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import {
|
|
7
|
+
import { Card, CardBody, ConfirmDeleteModal, Div, Input, PaginatedSelect, Select, Span, Stack, StackedViewShell, Text, Textarea, Toggle, useToast, Show, StickyToolbar } from "../../../ui/index.js";
|
|
8
8
|
import { usedPlaceholders } from "../../../_internal/shared/templating/placeholders.js";
|
|
9
9
|
import { apiClient } from "../../../http/index.js";
|
|
10
10
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
11
11
|
import { CategoryQuickCreateForm } from "./CategoryQuickCreateForm.js";
|
|
12
|
+
import { useMediaUpload } from "../../media/index.js";
|
|
13
|
+
import { ImageUpload } from "../../media/upload/ImageUpload.js";
|
|
14
|
+
import { Repeater } from "../../../ui/components/Repeater.js";
|
|
12
15
|
import { FieldInput, FormErrorSummary, FormShellContext, useFormShellState } from "../../../ui/forms/index.js";
|
|
13
16
|
import { SectionForm, useSectionFormNav } from "../../shell/index.js";
|
|
14
17
|
/*
|
|
@@ -59,17 +62,6 @@ const CONDITION_DEFAULT_OPTIONS = [
|
|
|
59
62
|
{ value: "used", label: "Used" },
|
|
60
63
|
{ value: "refurbished", label: "Refurbished" },
|
|
61
64
|
];
|
|
62
|
-
/**
|
|
63
|
-
* A minimal add/remove repeater.
|
|
64
|
-
*
|
|
65
|
-
* Deliberately NOT a new appkit primitive: three local shapes with three
|
|
66
|
-
* different row renderers is not yet the Rule of Three on a *component* — it
|
|
67
|
-
* is one container used three times with a render prop, which is what this is.
|
|
68
|
-
* Promote it when a fourth caller appears outside this file.
|
|
69
|
-
*/
|
|
70
|
-
function Repeater({ rows, onChange, blank, addLabel, renderRow, max, }) {
|
|
71
|
-
return (_jsxs(Stack, { gap: "sm", children: [rows.map((row, i) => (_jsx(Div, { rounded: "lg", border: "default", padding: "sm", children: _jsxs(Stack, { gap: "xs", children: [renderRow(row, (patch) => onChange(rows.map((r, j) => (j === i ? { ...r, ...patch } : r)))), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onChange(rows.filter((_, j) => j !== i)), children: "Remove" })] }) }, i))), rows.length < max ? (_jsx(Button, { variant: "secondary", size: "sm", onClick: () => onChange([...rows, blank()]), children: addLabel })) : (_jsxs(Text, { size: "xs", color: "muted", children: ["Maximum of ", max, " reached."] }))] }));
|
|
72
|
-
}
|
|
73
65
|
async function loadTaxCodeOptions(query, page) {
|
|
74
66
|
const params = new URLSearchParams({ page: String(page), pageSize: "25" });
|
|
75
67
|
if (query)
|
|
@@ -108,6 +100,36 @@ const categoryFormSchema = z.object({
|
|
|
108
100
|
.or(z.literal("")),
|
|
109
101
|
isActive: z.boolean().optional(),
|
|
110
102
|
showInMenu: z.boolean().optional(),
|
|
103
|
+
/*
|
|
104
|
+
* ── Discovery flags ──────────────────────────────────────────────────────
|
|
105
|
+
*
|
|
106
|
+
* All four are in `CATEGORIES_UPDATABLE_FIELDS`, so the repository has
|
|
107
|
+
* always accepted them — there was simply no form. An admin could not
|
|
108
|
+
* feature a category, set its priority, or take it out of search, on a
|
|
109
|
+
* taxonomy of 1,185 rows.
|
|
110
|
+
*/
|
|
111
|
+
isFeatured: z.boolean().optional(),
|
|
112
|
+
featuredPriority: z
|
|
113
|
+
.string()
|
|
114
|
+
.regex(/^\d*$/, "Priority must be a whole number")
|
|
115
|
+
.optional()
|
|
116
|
+
.or(z.literal("")),
|
|
117
|
+
isSearchable: z.boolean().optional(),
|
|
118
|
+
showOnHomepage: z.boolean().optional(),
|
|
119
|
+
/*
|
|
120
|
+
* `display.coverImage`, flattened into the draft because the form is flat.
|
|
121
|
+
*
|
|
122
|
+
* 🛑 THE DATA MODEL HAS ALWAYS CARRIED THIS AND THE EDITOR NEVER EXPOSED IT.
|
|
123
|
+
* 504 category covers live in Storage, `resolveTaxonomyVisual` reads
|
|
124
|
+
* `display.coverImage`, and the category page renders it — the only missing
|
|
125
|
+
* piece was a way for an admin to set or change one. That is why the
|
|
126
|
+
* taxonomy looked unfinished.
|
|
127
|
+
*
|
|
128
|
+
* Not validated as a URL: the value is either a `/media/<slug>` path minted
|
|
129
|
+
* by the upload flow or empty, and a stricter rule here would reject the
|
|
130
|
+
* former. An empty string is a legitimate value and means "cleared".
|
|
131
|
+
*/
|
|
132
|
+
coverImage: z.string().optional().or(z.literal("")),
|
|
111
133
|
// ── Content & Templates ──────────────────────────────────────────────────
|
|
112
134
|
contentBody: z
|
|
113
135
|
.string()
|
|
@@ -151,6 +173,13 @@ const EMPTY_VALUES = {
|
|
|
151
173
|
order: "",
|
|
152
174
|
isActive: true,
|
|
153
175
|
showInMenu: true,
|
|
176
|
+
isFeatured: false,
|
|
177
|
+
featuredPriority: "",
|
|
178
|
+
/* Searchable by DEFAULT — a new category nobody can find is a worse
|
|
179
|
+
default than one that appears in results. */
|
|
180
|
+
isSearchable: true,
|
|
181
|
+
showOnHomepage: false,
|
|
182
|
+
coverImage: "",
|
|
154
183
|
contentBody: "",
|
|
155
184
|
highlightsText: "",
|
|
156
185
|
hasbroName: "",
|
|
@@ -277,6 +306,7 @@ function toCategorySlug(str) {
|
|
|
277
306
|
export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedded, ...rest }) {
|
|
278
307
|
const isEdit = Boolean(categoryId);
|
|
279
308
|
const [deleteOpen, setDeleteOpen] = React.useState(false);
|
|
309
|
+
const { upload } = useMediaUpload();
|
|
280
310
|
const [values, setValues] = React.useState(EMPTY_VALUES);
|
|
281
311
|
/*
|
|
282
312
|
* The three repeater arrays live OUTSIDE `values`.
|
|
@@ -330,6 +360,11 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
330
360
|
order: cat.order !== undefined ? String(cat.order) : "",
|
|
331
361
|
isActive: cat.isActive ?? true,
|
|
332
362
|
showInMenu: cat.display?.showInMenu ?? true,
|
|
363
|
+
isFeatured: cat.isFeatured ?? false,
|
|
364
|
+
featuredPriority: cat.featuredPriority !== undefined ? String(cat.featuredPriority) : "",
|
|
365
|
+
isSearchable: cat.isSearchable ?? true,
|
|
366
|
+
showOnHomepage: cat.showOnHomepage ?? false,
|
|
367
|
+
coverImage: cat.display?.coverImage ?? "",
|
|
333
368
|
contentBody: cat.contentBody ?? "",
|
|
334
369
|
highlightsText: listToLines(cat.highlights),
|
|
335
370
|
hasbroName: cat.hasbroName ?? "",
|
|
@@ -366,7 +401,22 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
366
401
|
parentId: String(values.parentId ?? "") || undefined,
|
|
367
402
|
order: values.order !== "" && values.order !== undefined ? Number(values.order) : undefined,
|
|
368
403
|
isActive: values.isActive ?? true,
|
|
369
|
-
|
|
404
|
+
isFeatured: values.isFeatured ?? false,
|
|
405
|
+
featuredPriority: values.featuredPriority !== "" && values.featuredPriority !== undefined
|
|
406
|
+
? Number(values.featuredPriority)
|
|
407
|
+
: undefined,
|
|
408
|
+
isSearchable: values.isSearchable ?? true,
|
|
409
|
+
showOnHomepage: values.showOnHomepage ?? false,
|
|
410
|
+
/*
|
|
411
|
+
* `coverImage` is sent even when empty — an empty string is how the
|
|
412
|
+
* admin says "remove the cover", and omitting it would make clearing
|
|
413
|
+
* the field a no-op now that the route merges rather than replaces.
|
|
414
|
+
* Every reader trims and tests truthiness, so `""` reads as absent.
|
|
415
|
+
*/
|
|
416
|
+
display: {
|
|
417
|
+
showInMenu: values.showInMenu ?? true,
|
|
418
|
+
coverImage: String(values.coverImage ?? ""),
|
|
419
|
+
},
|
|
370
420
|
};
|
|
371
421
|
if (isEdit) {
|
|
372
422
|
/*
|
|
@@ -434,8 +484,14 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
434
484
|
{
|
|
435
485
|
id: "visibility",
|
|
436
486
|
label: "Display",
|
|
437
|
-
fields: ["order", "isActive", "showInMenu"],
|
|
438
|
-
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(
|
|
487
|
+
fields: ["order", "isActive", "showInMenu", "coverImage", "isFeatured", "featuredPriority", "isSearchable", "showOnHomepage"],
|
|
488
|
+
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(ImageUpload, { label: "Cover image", helperText: "Shown on the category page hero and on category cards.", currentImage: String(v.coverImage ?? ""), onUpload: (file) => upload(file, "categories", true, {
|
|
489
|
+
type: "category-image",
|
|
490
|
+
// The filename is content-derived, so re-uploading for the
|
|
491
|
+
// same category overwrites at the same storage path rather
|
|
492
|
+
// than orphaning the previous object.
|
|
493
|
+
name: String(v.name ?? "") || String(v.slug ?? ""),
|
|
494
|
+
}), onChange: (url) => onChange({ coverImage: url }) }), _jsx(Input, { label: "Display order", value: String(v.order ?? ""), onChange: (e) => onChange({ order: e.target.value }), type: "number", min: 0, placeholder: "0", error: errors.order }), _jsx(Toggle, { label: "Active", checked: v.isActive ?? true, onChange: (checked) => onChange({ isActive: checked }) }), _jsx(Toggle, { label: "Show in menu", checked: v.showInMenu ?? true, onChange: (checked) => onChange({ showInMenu: checked }) }), _jsx(Toggle, { label: "Searchable", checked: v.isSearchable ?? true, onChange: (checked) => onChange({ isSearchable: checked }) }), _jsx(Toggle, { label: "Featured", checked: v.isFeatured ?? false, onChange: (checked) => onChange({ isFeatured: checked }) }), (v.isFeatured ?? false) ? (_jsx(Input, { label: "Featured priority", value: String(v.featuredPriority ?? ""), onChange: (e) => onChange({ featuredPriority: e.target.value }), type: "number", min: 0, placeholder: "0", helperText: "Lower sorts first among featured categories.", error: errors.featuredPriority })) : null, _jsx(Toggle, { label: "Show on homepage", checked: v.showOnHomepage ?? false, onChange: (checked) => onChange({ showOnHomepage: checked }) })] })),
|
|
439
495
|
},
|
|
440
496
|
/*
|
|
441
497
|
* 🛑 EDIT ONLY. The create route's `z.object()` would strip every one of
|
|
@@ -448,7 +504,7 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
448
504
|
id: "content",
|
|
449
505
|
label: "Content & Templates",
|
|
450
506
|
fields: ["contentBody", "highlightsText", "hasbroName"],
|
|
451
|
-
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(
|
|
507
|
+
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { label: "Category page body", value: String(v.contentBody ?? ""), onChange: (e) => onChange({ contentBody: e.target.value }), rows: 8, placeholder: "400\u2013900 characters of keyword-rich HTML with links to sibling and parent categories\u2026" }), errors.contentBody ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.contentBody })) : (_jsx(Text, { size: "xs", color: "muted", children: "Rendered on the category page. Distinct from the short Description above, which also feeds the SEO meta and every card. 36 of worldhobbyshop's 46 categories carry one of these and it is their actual ranking mechanism; no Shopify competitor in the crawl has any." }))] }), _jsx(FieldInput, { name: "hasbroName", label: "Hasbro / international name", value: String(v.hasbroName ?? ""), onChange: (val) => onChange({ hasbroName: val }), placeholder: "e.g. Beyblade Burst Turbo", error: errors.hasbroName, hint: "Only when the Hasbro season name genuinely differs from the Takara-Tomy one \u2014 Cho-Z ships as Turbo, God as Evolution. Rendered on the category page as 'Also known as'. Leave blank if there is no divergence, or if you cannot confirm one: a guessed pairing reads as fact." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { label: "Highlights \u2014 one per line", value: String(v.highlightsText ?? ""), onChange: (e) => onChange({ highlightsText: e.target.value }), rows: 4, placeholder: "Genuine Takara Tomy stock\nChecked and photographed before listing" }), _jsx(Text, { size: "xs", color: "muted", children: "Short \"why shop this\" bullets, shown above the FAQs. Blank lines are dropped. Up to 12." })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Category FAQs" }), _jsx(Text, { size: "xs", color: "muted", children: "Rendered as an accordion on the category page and emitted as FAQPage structured data. Keep platform-wide answers in the global FAQ collection instead \u2014 two copies of one answer means the one that gets fixed is never the one being read." }), _jsx(Repeater, { rows: faqs, onChange: setFaqs, blank: () => ({ question: "", answer: "" }), addLabel: "Add an FAQ", max: 20, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Question", value: row.question, onChange: (e) => set({ question: e.target.value }), placeholder: "Are these compatible with Burst launchers?" }), _jsx(Textarea, { value: row.answer, onChange: (e) => set({ answer: e.target.value }), rows: 3, placeholder: "Answer\u2026" })] })) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Description templates" }), _jsxs(Text, { size: "xs", color: "muted", children: ["What the seller form offers when a listing is filed here. One per condition band \u2014 worldhobbyshop reuses exactly two across 1,195 listings. Slots like", " ", _jsx(Span, { family: "mono", children: "{{title}}" }), ",", " ", _jsx(Span, { family: "mono", children: "{{brand}}" }), ",", " ", _jsx(Span, { family: "mono", children: "{{condition}}" }), " and", " ", _jsx(Span, { family: "mono", children: "{{series}}" }), " are filled from the listing; an unresolved one becomes empty and is reported to the seller, never left as a literal brace pair."] }), _jsx(Repeater, { rows: templates, onChange: setTemplates, blank: () => ({ id: "", label: "", variant: "default", body: "" }), addLabel: "Add a template", max: 8, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Label", value: row.label, onChange: (e) => set({ label: e.target.value }), placeholder: "Pre-owned \u2014 condition as pictured" }), _jsx(Select, { label: "Condition band", value: row.variant, onChange: (e) => set({ variant: e.target.value }), options: TEMPLATE_VARIANT_OPTIONS })] }), _jsx(Textarea, { value: row.body, onChange: (e) => set({ body: e.target.value }), rows: 6, placeholder: "Important things to know about {{title}} \u2014 HTML permitted\u2026" }), _jsx(Text, { size: "xs", color: "muted", children: usedPlaceholders(row.body).length > 0
|
|
452
508
|
? `Slots used: ${usedPlaceholders(row.body).map((p) => `{{${p}}}`).join(", ")}`
|
|
453
509
|
: "No slots used — this body will read identically on every listing." })] })) })] })] })),
|
|
454
510
|
},
|
|
@@ -456,7 +512,7 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
456
512
|
id: "defaults",
|
|
457
513
|
label: "Product defaults",
|
|
458
514
|
fields: ["taxCodeId", "weightG", "dimL", "dimW", "dimH", "seoTitleTemplate", "seoDescriptionTemplate"],
|
|
459
|
-
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "What a listing filed under this category inherits. Written server-side when the product omits the field, and resolved PER FIELD up the ancestor chain \u2014 so a named-model leaf can supply its own features while the tax code and spec rows come from the line above it." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Tax code" }), _jsx(PaginatedSelect, { value: String(v.taxCodeId ?? "") || null, onChange: (val) => onChange({ taxCodeId: val ?? "" }), loadOptions: loadTaxCodeOptions, placeholder: "Search tax codes\u2026", searchPlaceholder: "HSN or label\u2026", noResultsText: "No tax codes found", ariaLabel: "Tax code" }), _jsx(Text, { size: "xs", color: "muted", children: "\uD83D\uDED1 This is the GST mechanism, not a nicety. A listing whose category resolves no tax code falls back to a default rate and is flagged for triage \u2014 and the measure of success is that the fallback fires for nothing." })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Select, { label: "Default condition", value: String(v.defaultCondition ?? ""), onChange: (e) => onChange({ defaultCondition: e.target.value }), options: CONDITION_DEFAULT_OPTIONS }), _jsx(Select, { label: "Default authenticity", value: String(v.defaultAuthenticity ?? ""), onChange: (e) => onChange({ defaultAuthenticity: e.target.value }), options: AUTHENTICITY_OPTIONS })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Specification rows" }), _jsx(Repeater, { rows: specs, onChange: setSpecs, blank: () => ({ name: "", value: "", unit: "" }), addLabel: "Add a specification", max: 20, renderRow: (row, set) => (_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-3", children: [_jsx(Input, { label: "Name", value: row.name, onChange: (e) => set({ name: e.target.value }), placeholder: "System" }), _jsx(Input, { label: "Value", value: row.value, onChange: (e) => set({ value: e.target.value }), placeholder: "Magne Core" }), _jsx(Input, { label: "Unit", value: row.unit ?? "", onChange: (e) => set({ unit: e.target.value }), placeholder: "g" })] })) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(
|
|
515
|
+
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "What a listing filed under this category inherits. Written server-side when the product omits the field, and resolved PER FIELD up the ancestor chain \u2014 so a named-model leaf can supply its own features while the tax code and spec rows come from the line above it." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Tax code" }), _jsx(PaginatedSelect, { value: String(v.taxCodeId ?? "") || null, onChange: (val) => onChange({ taxCodeId: val ?? "" }), loadOptions: loadTaxCodeOptions, placeholder: "Search tax codes\u2026", searchPlaceholder: "HSN or label\u2026", noResultsText: "No tax codes found", ariaLabel: "Tax code" }), _jsx(Text, { size: "xs", color: "muted", children: "\uD83D\uDED1 This is the GST mechanism, not a nicety. A listing whose category resolves no tax code falls back to a default rate and is flagged for triage \u2014 and the measure of success is that the fallback fires for nothing." })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Select, { label: "Default condition", value: String(v.defaultCondition ?? ""), onChange: (e) => onChange({ defaultCondition: e.target.value }), options: CONDITION_DEFAULT_OPTIONS }), _jsx(Select, { label: "Default authenticity", value: String(v.defaultAuthenticity ?? ""), onChange: (e) => onChange({ defaultAuthenticity: e.target.value }), options: AUTHENTICITY_OPTIONS })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Specification rows" }), _jsx(Repeater, { rows: specs, onChange: setSpecs, blank: () => ({ name: "", value: "", unit: "" }), addLabel: "Add a specification", max: 20, renderRow: (row, set) => (_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-3", children: [_jsx(Input, { label: "Name", value: row.name, onChange: (e) => set({ name: e.target.value }), placeholder: "System" }), _jsx(Input, { label: "Value", value: row.value, onChange: (e) => set({ value: e.target.value }), placeholder: "Magne Core" }), _jsx(Input, { label: "Unit", value: row.unit ?? "", onChange: (e) => set({ unit: e.target.value }), placeholder: "g" })] })) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { label: "What's in the box \u2014 one per line", value: String(v.inTheBoxText ?? ""), onChange: (e) => onChange({ inTheBoxText: e.target.value }), rows: 4, placeholder: "Beyblade top\nLauncher\nRipcord\nManual" }), _jsx(Text, { size: "xs", color: "muted", children: "Read by the description-template endpoint to fill the new-in-box body's list. Deliberately NOT written to the product's `features` field, which holds feature ids \u2014 putting prose there would break every facet on the listing." })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "SEO title template", value: String(v.seoTitleTemplate ?? ""), onChange: (e) => onChange({ seoTitleTemplate: e.target.value }), placeholder: "{{title}} \u2014 {{brand}} | {{siteName}}", error: errors.seoTitleTemplate }), _jsx(Input, { label: "SEO description template", value: String(v.seoDescriptionTemplate ?? ""), onChange: (e) => onChange({ seoDescriptionTemplate: e.target.value }), placeholder: "Buy {{title}} in India\u2026", error: errors.seoDescriptionTemplate })] }), _jsx(Stack, { gap: "xs", children: _jsx(Textarea, { label: "SEO keywords \u2014 one per line", value: String(v.seoKeywordsText ?? ""), onChange: (e) => onChange({ seoKeywordsText: e.target.value }), rows: 3 }) }), _jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-4", children: [_jsx(Input, { label: "Packed weight (g)", value: String(v.weightG ?? ""), onChange: (e) => onChange({ weightG: e.target.value }), placeholder: "300", error: errors.weightG, helperText: "Boxed, not the bare item." }), _jsx(Input, { label: "Length (cm)", value: String(v.dimL ?? ""), onChange: (e) => onChange({ dimL: e.target.value }), error: errors.dimL }), _jsx(Input, { label: "Width (cm)", value: String(v.dimW ?? ""), onChange: (e) => onChange({ dimW: e.target.value }), error: errors.dimW }), _jsx(Input, { label: "Height (cm)", value: String(v.dimH ?? ""), onChange: (e) => onChange({ dimH: e.target.value }), error: errors.dimH })] }), _jsx(Text, { size: "xs", color: "muted", children: "\uD83D\uDED1 The weight is a PACKED weight \u2014 a bey is ~30 g and ~300 g boxed. This figure drives the shipping estimate the buyer pays, and the seller's booking is capped at what was collected, so an item weight here under-estimates every booking and the difference is absorbed silently. All three dimensions or none." }), _jsx(Toggle, { label: "Fragile by default", checked: v.fragile === true, onChange: (checked) => onChange({ fragile: checked }) })] })),
|
|
460
516
|
},
|
|
461
517
|
]
|
|
462
518
|
: []),
|
|
@@ -29,7 +29,7 @@ export function AdminContactEditorView({ open, onClose, submissionId, subject, n
|
|
|
29
29
|
});
|
|
30
30
|
const statusColor = {
|
|
31
31
|
new: "bg-info-surface text-info",
|
|
32
|
-
read: "bg-[var(--appkit-color-surface
|
|
32
|
+
read: "bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]",
|
|
33
33
|
resolved: "bg-success-surface text-success",
|
|
34
34
|
};
|
|
35
35
|
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: subject ?? "Contact Submission", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "5", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Text, { size: "xs", color: "muted", children: "Status:" }), _jsx(Span, { layout: "inline-flex", className: `${statusColor[currentStatus ?? "new"] ?? statusColor.new}`, size: "xs", weight: "medium", rounded: "full", padding: "pill-xs", children: currentStatus ?? "new" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "medium", transform: "uppercase", children: "From" }), _jsx(Text, { size: "sm", weight: "medium", color: "primary", children: name ?? "Unknown" }), email && (_jsx(Text, { size: "sm", color: "muted", children: email }))] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "medium", transform: "uppercase", children: "Message" }), _jsx(Div, { className: `${__P.p3} max-h-64 ${__O.yAuto}`, rounded: "lg", surface: "muted", border: "default", children: _jsx(Text, { className: "whitespace-pre-wrap leading-relaxed", color: "primary", size: "sm", children: message ?? "No message body." }) })] }), _jsxs(FormActions, { align: "right", children: [email && (_jsx(Button, { type: "button", variant: "outline", onClick: () => {
|
|
@@ -4,7 +4,8 @@ import { normalizeError } from "../../../errors/normalize.js";
|
|
|
4
4
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { useQuery } from "@tanstack/react-query";
|
|
7
|
-
import { ConfirmDeleteModal, Div, StackedViewShell, useToast } from "../../../ui/index.js";
|
|
7
|
+
import { ConfirmDeleteModal, Div, Input, StackedViewShell, useToast } from "../../../ui/index.js";
|
|
8
|
+
import { Repeater } from "../../../ui/components/Repeater.js";
|
|
8
9
|
import { FieldInput, FormErrorSummary } from "../../../ui/forms/index.js";
|
|
9
10
|
import { apiClient } from "../../../http/index.js";
|
|
10
11
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
@@ -63,6 +64,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
63
64
|
isActive: true,
|
|
64
65
|
firstTimeUserOnly: false,
|
|
65
66
|
appliesToAuctions: false,
|
|
67
|
+
tiers: [],
|
|
66
68
|
applicableProducts: [],
|
|
67
69
|
applicableCategories: [],
|
|
68
70
|
});
|
|
@@ -113,6 +115,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
113
115
|
? c.restrictions.applicableCategories
|
|
114
116
|
: [],
|
|
115
117
|
appliesToAuctions: c.applicableToAuctions ?? false,
|
|
118
|
+
tiers: c.tiers ?? [],
|
|
116
119
|
});
|
|
117
120
|
}, [couponQuery.data]);
|
|
118
121
|
const handleNameChange = (value) => {
|
|
@@ -166,6 +169,13 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
166
169
|
}),
|
|
167
170
|
},
|
|
168
171
|
applicableToAuctions: form.appliesToAuctions,
|
|
172
|
+
/*
|
|
173
|
+
* 🛑 Sent even when EMPTY so that clearing the last tier persists —
|
|
174
|
+
* the same reasoning as the brand highlights. A tier with no
|
|
175
|
+
* threshold and no value is dropped rather than stored: it would be
|
|
176
|
+
* a band that matches every order and discounts nothing.
|
|
177
|
+
*/
|
|
178
|
+
tiers: form.tiers.filter((t) => t.minAmount > 0 || t.discountValue > 0),
|
|
169
179
|
};
|
|
170
180
|
if (isEdit) {
|
|
171
181
|
return apiClient.patch(ADMIN_ENDPOINTS.COUPON_BY_ID(couponId), payload);
|
|
@@ -208,6 +218,21 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
208
218
|
const sections = React.useMemo(() => buildSectionsFromSchema(adminCouponFormSchema, {
|
|
209
219
|
options: { type: TYPE_OPTIONS.map((o) => ({ value: String(o.value), label: o.label })) },
|
|
210
220
|
renderers: {
|
|
221
|
+
/*
|
|
222
|
+
* Spend tiers. `Repeater` is the one promoted out of
|
|
223
|
+
* `AdminCategoryEditorView`; this is its fifth caller.
|
|
224
|
+
*
|
|
225
|
+
* 🛑 Both inputs are NUMBERS, parsed here rather than kept as
|
|
226
|
+
* strings, because `getTieredDiscount` sorts on `minAmount` and
|
|
227
|
+
* compares it to the order total. A string "1000" sorts before
|
|
228
|
+
* "900" lexically, which would silently pick the wrong band.
|
|
229
|
+
*/
|
|
230
|
+
tiers: ({ values, onChange }) => {
|
|
231
|
+
const rows = values.tiers ?? [];
|
|
232
|
+
return (_jsx(Repeater, { rows: rows, onChange: (next) => onChange({ tiers: next }), blank: () => ({ minAmount: 0, discountValue: 0 }), addLabel: "Add a tier", max: 6, renderRow: (row, set) => (_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Spend at least (\u20B9)", type: "number", min: 0, value: String(row.minAmount), onChange: (e) => set({ minAmount: Number(e.target.value) || 0 }) }), _jsx(Input, { label: values.type === "percentage"
|
|
233
|
+
? "Discount (%)"
|
|
234
|
+
: "Discount (₹)", type: "number", min: 0, value: String(row.discountValue), onChange: (e) => set({ discountValue: Number(e.target.value) || 0 }) })] })) }));
|
|
235
|
+
},
|
|
211
236
|
// Typing the campaign name fills the code until the admin edits it.
|
|
212
237
|
name: ({ values, errors }) => (_jsx(FieldInput, { name: "name", label: "Campaign name", required: true, value: values.name, error: errors.name, onChange: handleNameChange })),
|
|
213
238
|
code: ({ values, onChange, errors }) => (_jsx(FieldInput, { name: "code", label: "Coupon code", required: true, disabled: isEdit, hint: isEdit ? "A code cannot change once customers have it." : undefined, value: values.code, error: errors.code, onChange: (v) => {
|
|
@@ -187,7 +187,7 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
|
|
|
187
187
|
render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "question", label: "Question", value: String(v.question ?? ""), onChange: (val) => onChange({ question: val }), required: true, placeholder: "e.g. How does bidding work on LetItRip?", error: errors.question }), _jsx(Input, { label: "Slug", value: String(v.slug ?? ""), onChange: (e) => {
|
|
188
188
|
setSlugManual(true);
|
|
189
189
|
onChange({ slug: e.target.value });
|
|
190
|
-
}, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from the question until you edit it. Must start with 'faq-'.", error: errors.slug }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Answer" }), _jsx(RichTextEditor, { value: String(v.answer ?? ""), onChange: (val) => onChange({ answer: val }), placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" }), errors.answer && (_jsx(Text, { size: "xs", color: "error", role: "alert", children: errors.answer }))] })] })),
|
|
190
|
+
}, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from the question until you edit it. Must start with 'faq-'.", error: errors.slug }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Answer" }), _jsx(RichTextEditor, { ariaLabel: "Answer", value: String(v.answer ?? ""), onChange: (val) => onChange({ answer: val }), placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" }), errors.answer && (_jsx(Text, { size: "xs", color: "error", role: "alert", children: errors.answer }))] })] })),
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
id: "classification",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit/client";
|
|
4
4
|
import { sortBy } from "@mohasinac/appkit/client";
|
|
5
5
|
import React, { useMemo } from "react";
|
|
@@ -101,5 +101,5 @@ export function AdminFaqsView({ children, onBulkArchive, onBulkDelete, ...props
|
|
|
101
101
|
if (React.Children.count(children) > 0) {
|
|
102
102
|
return (_jsxs(ListingLayout, { portal: "admin", ...props, children: [_jsx(Heading, { level: 1, className: "sr-only", children: "FAQs" }), children] }));
|
|
103
103
|
}
|
|
104
|
-
return
|
|
104
|
+
return _jsx(DataListingView, { config: config });
|
|
105
105
|
}
|