@mohasinac/appkit 4.10.0 → 4.11.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/_internal/client/features/layout/DashboardLayoutClient.d.ts +6 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
- package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
- package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
- package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
- package/dist/_internal/server/features/audit-log/actions.js +38 -0
- package/dist/_internal/server/features/brands/actions.js +0 -2
- package/dist/_internal/server/features/brands/og.d.ts +4 -1
- package/dist/_internal/server/features/brands/og.js +1 -1
- package/dist/_internal/server/features/grouped/data.d.ts +4 -0
- package/dist/_internal/server/features/grouped/data.js +24 -0
- package/dist/_internal/server/features/grouped/index.d.ts +1 -1
- package/dist/_internal/server/features/grouped/index.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +9 -0
- package/dist/_internal/server/features/orders/adapters.js +25 -0
- package/dist/_internal/server/functions/scheduled.d.ts +3 -1
- package/dist/_internal/server/functions/scheduled.js +17 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
- package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/index.js +3 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
- package/dist/_internal/server/notifications/channel-health.js +101 -0
- package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
- package/dist/_internal/shared/features/brands/schema.js +0 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/client.d.ts +21 -6
- package/dist/client.js +13 -3
- package/dist/constants/api-endpoints.d.ts +6 -9
- package/dist/constants/api-endpoints.js +2 -3
- package/dist/contracts/client-auth.d.ts +3 -7
- package/dist/contracts/email.d.ts +1 -0
- package/dist/features/account/components/AddressForm.js +24 -2
- package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
- package/dist/features/account/components/BecomeSellerView.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserBidsView.d.ts +1 -0
- package/dist/features/account/components/UserBidsView.js +47 -0
- package/dist/features/account/components/UserOrdersView.d.ts +4 -11
- package/dist/features/account/components/UserOrdersView.js +54 -4
- package/dist/features/account/components/UserReturnsView.d.ts +4 -9
- package/dist/features/account/components/UserReturnsView.js +37 -3
- package/dist/features/account/components/index.d.ts +3 -2
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/admin/actions/admin-actions.js +22 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
- package/dist/features/admin/actions/notification-actions.js +4 -3
- package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
- package/dist/features/admin/components/AdminAdsView.js +86 -89
- package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
- package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
- package/dist/features/admin/components/AdminAuditLogView.js +95 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
- package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
- package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
- package/dist/features/admin/components/AdminCartsView.js +34 -4
- package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminHistoryView.js +11 -1
- package/dist/features/admin/components/AdminNotificationsView.js +27 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +92 -24
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
- package/dist/features/admin/components/AdminPayoutsView.js +15 -31
- package/dist/features/admin/components/AdminProductEditorView.js +26 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
- package/dist/features/admin/components/AdminStoresView.js +7 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
- package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
- package/dist/features/admin/components/AdminUserEditorView.js +4 -2
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +14 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -2
- package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
- package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
- package/dist/features/admin/components/ViewNotificationModal.js +15 -0
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
- package/dist/features/admin/components/index.d.ts +8 -1
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
- package/dist/features/admin/constants/filter-tabs.js +27 -0
- package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
- package/dist/features/admin/hooks/useAdminListing.js +3 -1
- package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
- package/dist/features/admin/hooks/useAdminListingData.js +9 -2
- package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.d.ts +12 -0
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +8 -0
- package/dist/features/auctions/schemas/index.d.ts +5 -0
- package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
- package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
- package/dist/features/audit-log/schemas/firestore.js +35 -0
- package/dist/features/auth/actions/index.d.ts +0 -1
- package/dist/features/auth/actions/index.js +0 -1
- package/dist/features/auth/hooks/useAuth.d.ts +8 -35
- package/dist/features/auth/hooks/useAuth.js +21 -37
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +2 -0
- package/dist/features/blog/schemas/index.d.ts +32 -0
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
- package/dist/features/categories/components/BrandDetailPageView.js +14 -12
- package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
- package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
- package/dist/features/categories/components/CategoryProductsView.js +1 -1
- package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
- package/dist/features/categories/schemas/firestore.d.ts +19 -2
- package/dist/features/categories/schemas/index.d.ts +24 -3
- package/dist/features/categories/schemas/index.js +3 -1
- package/dist/features/categories/types/index.d.ts +7 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
- package/dist/features/contact/components/ContactForm.js +17 -4
- package/dist/features/contact/email.d.ts +1 -0
- package/dist/features/contact/email.js +3 -0
- package/dist/features/events/components/AdminEventEditorView.js +54 -2
- package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
- package/dist/features/events/components/AdminEventEntriesView.js +56 -89
- package/dist/features/events/components/EventFormDrawer.js +5 -1
- package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
- package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
- package/dist/features/events/schemas/index.d.ts +22 -0
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +4 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +18 -0
- package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
- package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/layout/TitleBarLayout.d.ts +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/media/MediaImage.js +39 -3
- package/dist/features/media/types/index.d.ts +10 -0
- package/dist/features/media/types/index.js +1 -0
- package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
- package/dist/features/media/upload/MediaUploadField.js +11 -7
- package/dist/features/media/upload/video-poster.d.ts +2 -0
- package/dist/features/media/upload/video-poster.js +16 -1
- package/dist/features/media/upload/video-poster.test.js +1 -1
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
- package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/constants/payment-window.d.ts +25 -0
- package/dist/features/orders/constants/payment-window.js +25 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +56 -0
- package/dist/features/orders/schemas/index.d.ts +24 -24
- package/dist/features/orders/types/index.d.ts +33 -0
- package/dist/features/pre-orders/schemas/index.d.ts +5 -0
- package/dist/features/products/components/BidHistory.js +9 -1
- package/dist/features/products/components/GroupSettingsPanel.js +25 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
- package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
- package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
- package/dist/features/products/components/ProductDetailActions.js +1 -1
- package/dist/features/products/components/ProductGrid.js +15 -1
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/index.d.ts +5 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/search/components/SearchView.d.ts +1 -3
- package/dist/features/search/components/SearchView.js +1 -1
- package/dist/features/seller/components/QuickProductForm.js +2 -1
- package/dist/features/seller/components/SellerBidsView.js +66 -107
- package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
- package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
- package/dist/features/seller/components/SellerOrdersView.js +134 -163
- package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
- package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
- package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
- package/dist/features/seller/components/SellerPayoutsView.js +25 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
- package/dist/features/seller/components/SellerProductShell.js +89 -36
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
- package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
- package/dist/features/seller/components/SellerProductsView.js +131 -189
- package/dist/features/seller/components/SellerReviewsView.js +95 -133
- package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
- package/dist/features/seller/components/SellerShippingView.js +41 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
- package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
- package/dist/features/seller/components/SellerStorefrontView.js +42 -5
- package/dist/features/seller/components/index.d.ts +2 -2
- package/dist/features/seller/components/index.js +2 -2
- package/dist/features/shell/FormShell.d.ts +24 -4
- package/dist/features/shell/FormShell.js +12 -6
- package/dist/features/shell/QuickFormDrawer.js +67 -12
- package/dist/features/shell/StepForm.d.ts +30 -1
- package/dist/features/shell/StepForm.js +47 -4
- package/dist/features/shell/index.d.ts +1 -1
- package/dist/features/shell/index.js +1 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
- package/dist/features/shipments/schemas/validation.d.ts +14 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
- package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
- package/dist/features/tester/components/TesterHubView.js +3 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
- package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +404 -15
- package/dist/index.d.ts +15 -3
- package/dist/index.js +22 -5
- package/dist/next/routing/route-map.d.ts +11 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/providers/email-resend/provider.js +1 -0
- package/dist/providers/firebase-client/auth.d.ts +1 -0
- package/dist/providers/firebase-client/auth.js +7 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +61 -25
- package/dist/security/pii-mask.js +1 -1
- package/dist/seed/categories-seed-data.js +69 -0
- package/dist/seed/products-auctions-seed-data.js +11 -0
- package/dist/seed/products-standard-seed-data.js +93 -4
- package/dist/seed/site-settings-seed-data.js +13 -1
- package/dist/seed/store-extensions-seed-data.js +1 -1
- package/dist/server-entry.d.ts +2 -1
- package/dist/server-entry.js +20 -2
- package/dist/server.d.ts +7 -2
- package/dist/server.js +8 -3
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +11 -2
- package/dist/ui/components/Form.js +4 -3
- package/dist/ui/components/FormField.d.ts +3 -1
- package/dist/ui/components/FormField.js +22 -4
- package/dist/ui/components/ListingLayout.d.ts +1 -2
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/components/RecordDetailModal.d.ts +68 -0
- package/dist/ui/components/RecordDetailModal.js +8 -0
- package/dist/ui/components/SlottedListingView.js +9 -1
- package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
- package/dist/ui/forms/FormErrorSummary.js +37 -0
- package/dist/ui/forms/FormShell.d.ts +37 -2
- package/dist/ui/forms/FormShell.js +37 -7
- package/dist/ui/forms/FormShell.style.css +17 -0
- package/dist/ui/forms/index.d.ts +2 -0
- package/dist/ui/forms/index.js +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +5 -0
- package/dist/utils/media-url.d.ts +3 -1
- package/dist/utils/media-url.js +20 -1
- package/dist/validation/schemas.d.ts +4 -4
- package/package.json +1 -1
- package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
- package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
- package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
- package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
- package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
- package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
- package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
- package/dist/features/auth/consent-otp.d.ts +0 -57
- package/dist/features/auth/consent-otp.js +0 -127
- package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
- package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
- package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
- package/dist/features/checkout/actions/checkout-actions.js +0 -124
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
- package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
- package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
- package/dist/features/classified/components/ClassifiedListView.js +0 -44
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
- package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
- package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
- package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
- package/dist/features/live/components/LiveItemsListView.js +0 -44
- package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
- package/dist/features/seller/components/SellerTemplatesView.js +0 -276
- package/dist/react/hooks/useVisibleItems.d.ts +0 -37
- package/dist/react/hooks/useVisibleItems.js +0 -59
- package/dist/ui/components/TabStrip.d.ts +0 -34
- package/dist/ui/components/TabStrip.js +0 -56
- package/dist/ui/components/TabStrip.style.css +0 -97
|
@@ -35,7 +35,12 @@ export interface DashboardLayoutClientProps {
|
|
|
35
35
|
permissions?: string[] | null;
|
|
36
36
|
/** Override active-link highlight. Defaults to usePathname(). */
|
|
37
37
|
activeHref?: string;
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Responsive controls — accepted for interface parity with the generic
|
|
40
|
+
* `SectionResponsive` shape used elsewhere, but not currently honoured by
|
|
41
|
+
* this component (no caller passes it today). Wire `hideAt`/`showAt`/
|
|
42
|
+
* `compactAt` to real chrome behaviour before relying on this prop.
|
|
43
|
+
*/
|
|
39
44
|
responsive?: SectionResponsive;
|
|
40
45
|
/**
|
|
41
46
|
* Cross-dashboard nav links shown in the sidebar footer. Plain hrefs
|
|
@@ -155,7 +155,10 @@ function DashboardBottomNav({ items, activeHref }) {
|
|
|
155
155
|
const DEFAULT_CONTENT_PADDING_RIGHT = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pl-14 lg:pr-6 xl:pl-16 xl:pr-10";
|
|
156
156
|
const DEFAULT_CONTENT_PADDING_LEFT = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pr-14 lg:pl-6 xl:pr-16 xl:pl-10";
|
|
157
157
|
const DEFAULT_CONTENT_MAX_WIDTH = "max-w-screen-2xl";
|
|
158
|
-
export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref,
|
|
158
|
+
export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref,
|
|
159
|
+
// audit-dead-underscore-prop-ok: accepted for interface parity, not yet
|
|
160
|
+
// wired — see the doc comment on `responsive` above; no caller passes it.
|
|
161
|
+
responsive: _responsive, crossNav, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
|
|
159
162
|
const pathname = usePathname();
|
|
160
163
|
const activeHref = explicitActiveHref ?? pathname ?? "";
|
|
161
164
|
const storageKey = `appkit:sidebar-open:${variant}`;
|
|
@@ -16,5 +16,5 @@ interface LotteryPullFormProps {
|
|
|
16
16
|
* Submits TX ID + phone + payment time + item number.
|
|
17
17
|
* On success, shows the user their lottery number and assigned prize slot.
|
|
18
18
|
*/
|
|
19
|
-
export declare function LotteryPullForm({ sourceType, eventId, productId, totalSlots, maxPullsPerUser
|
|
19
|
+
export declare function LotteryPullForm({ sourceType, eventId, productId, totalSlots, maxPullsPerUser, onSuccess, }: LotteryPullFormProps): React.JSX.Element;
|
|
20
20
|
export {};
|
|
@@ -10,7 +10,7 @@ import { apiClient } from "../../../../http";
|
|
|
10
10
|
* Submits TX ID + phone + payment time + item number.
|
|
11
11
|
* On success, shows the user their lottery number and assigned prize slot.
|
|
12
12
|
*/
|
|
13
|
-
export function LotteryPullForm({ sourceType, eventId, productId, totalSlots, maxPullsPerUser
|
|
13
|
+
export function LotteryPullForm({ sourceType, eventId, productId, totalSlots, maxPullsPerUser, onSuccess, }) {
|
|
14
14
|
const [result, setResult] = useState(null);
|
|
15
15
|
const [transactionId, setTransactionId] = useState("");
|
|
16
16
|
const [paymentTime, setPaymentTime] = useState("");
|
|
@@ -39,7 +39,7 @@ export function LotteryPullForm({ sourceType, eventId, productId, totalSlots, ma
|
|
|
39
39
|
if (result) {
|
|
40
40
|
return (_jsxs(Stack, { gap: "sm", padding: "md", rounded: "2xl", surface: "success-surface", className: "border border-success/20", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { layout: "inline-flex", color: "inverse", weight: "semibold", className: "bg-success text-[11px] tracking-wide", padding: "pill-sm", rounded: "full", transform: "uppercase", children: "Entry Confirmed" }), _jsxs(Heading, { level: 2, className: "text-success", size: "lg", weight: "bold", children: ["You're #", result.userLotteryNumber, "!"] })] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["You've been assigned", " ", _jsxs(Span, { weight: "bold", children: ["Slot #", result.assignedPrizeSlotNumber] }), result.slotName ? ` — ${result.slotName}` : "", ". Good luck!"] })] }));
|
|
41
41
|
}
|
|
42
|
-
return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-4", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, size: "lg", weight: "semibold", children: "Submit Your Entry" }),
|
|
42
|
+
return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-4", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, size: "lg", weight: "semibold", children: "Submit Your Entry" }), _jsxs(Text, { size: "sm", color: "muted", children: ["Enter your UPI Transaction ID and payment details to claim your slot.", " ", "You may enter up to ", maxPullsPerUser, " time", maxPullsPerUser === 1 ? "" : "s", "."] }), _jsx(FieldInput, { name: "transactionId", label: "Transaction ID (UPI Ref / Payment Ref)", placeholder: "e.g. 123456789012", required: true, value: transactionId, onChange: (v) => { setTransactionId(v); clearErrors(); } }), _jsx(FieldInput, { name: "paymentTime", label: "Payment Date & Time", type: "datetime-local", required: true, value: paymentTime, onChange: (v) => { setPaymentTime(v); clearErrors(); } }), _jsx(FieldInput, { name: "itemNumber", label: `Slot Number (1 – ${totalSlots})`, type: "number", placeholder: `Enter a number between 1 and ${totalSlots}`, required: true, value: itemNumber, onChange: (v) => { setItemNumber(v); clearErrors(); } }), _jsx(FieldInput, { name: "userPhone", label: "Your Phone Number", type: "tel", placeholder: "e.g. 9876543210", required: true, value: userPhone, onChange: (v) => { setUserPhone(v); clearErrors(); } }), _jsx(Button, { type: "submit", variant: "primary", isLoading: mutation.isPending, onClick: () => {
|
|
43
43
|
clearErrors();
|
|
44
44
|
const num = parseInt(itemNumber, 10);
|
|
45
45
|
let valid = true;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual trigger for the daily status digest.
|
|
3
|
+
*
|
|
4
|
+
* The digest normally runs from the `dailyStatusDigest` scheduled Firebase
|
|
5
|
+
* Function (10:00 IST). Firebase has no "run on first deploy" trigger, so
|
|
6
|
+
* this exists to fire the exact same core function on demand — e.g. right
|
|
7
|
+
* after a fresh deploy, to confirm the whole chain works without waiting
|
|
8
|
+
* for the next morning. Both paths share one implementation; there is no
|
|
9
|
+
* second copy of the digest logic.
|
|
10
|
+
*/
|
|
11
|
+
import { type DailyStatusDigestResult } from "../../jobs/core/dailyStatusDigest";
|
|
12
|
+
export declare function triggerDailyStatusDigest(): Promise<DailyStatusDigestResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Fires the digest once per deployment version, from server startup.
|
|
15
|
+
* Safe to call on every cold start — `runDeploymentDigest` claims the
|
|
16
|
+
* version in a Firestore transaction and no-ops if it's already been sent.
|
|
17
|
+
*/
|
|
18
|
+
export declare function triggerDeploymentDigest(version: string): Promise<DailyStatusDigestResult>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual trigger for the daily status digest.
|
|
3
|
+
*
|
|
4
|
+
* The digest normally runs from the `dailyStatusDigest` scheduled Firebase
|
|
5
|
+
* Function (10:00 IST). Firebase has no "run on first deploy" trigger, so
|
|
6
|
+
* this exists to fire the exact same core function on demand — e.g. right
|
|
7
|
+
* after a fresh deploy, to confirm the whole chain works without waiting
|
|
8
|
+
* for the next morning. Both paths share one implementation; there is no
|
|
9
|
+
* second copy of the digest logic.
|
|
10
|
+
*/
|
|
11
|
+
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
12
|
+
import { serverLogger } from "../../../../monitoring";
|
|
13
|
+
import { runDailyStatusDigest, runDeploymentDigest, } from "../../jobs/core/dailyStatusDigest";
|
|
14
|
+
function buildManualJobContext(job) {
|
|
15
|
+
return {
|
|
16
|
+
job,
|
|
17
|
+
db: getAdminDb(),
|
|
18
|
+
logger: {
|
|
19
|
+
info: (message, meta) => serverLogger.info(message, meta),
|
|
20
|
+
warn: (message, meta) => serverLogger.warn(message, meta),
|
|
21
|
+
error: (message, err, meta) => serverLogger.error(message, { err, ...(meta ?? {}) }),
|
|
22
|
+
},
|
|
23
|
+
env: (key) => process.env[key],
|
|
24
|
+
now: new Date(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function triggerDailyStatusDigest() {
|
|
28
|
+
return runDailyStatusDigest(buildManualJobContext("dailyStatusDigest:manual"));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fires the digest once per deployment version, from server startup.
|
|
32
|
+
* Safe to call on every cold start — `runDeploymentDigest` claims the
|
|
33
|
+
* version in a Firestore transaction and no-ops if it's already been sent.
|
|
34
|
+
*/
|
|
35
|
+
export async function triggerDeploymentDigest(version) {
|
|
36
|
+
return runDeploymentDigest(buildManualJobContext("dailyStatusDigest:deploy"), version);
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recordAdminAction — the single write-site for the admin audit trail.
|
|
3
|
+
*
|
|
4
|
+
* MVP scope: instruments the highest-value already-privileged admin actions
|
|
5
|
+
* (bans, checkout bypass, coupon edits, payout mark-paid, store status
|
|
6
|
+
* changes, role changes) rather than sweeping every write path in the app —
|
|
7
|
+
* see CLAUDE.md's "Admin Audit Log" section for the full instrumented-action
|
|
8
|
+
* list and rationale.
|
|
9
|
+
*
|
|
10
|
+
* Callers should treat this as best-effort/non-blocking — a failure to write
|
|
11
|
+
* an audit entry must never fail the underlying privileged action itself.
|
|
12
|
+
*/
|
|
13
|
+
import type { AdminAuditAction } from "../../../../features/audit-log/schemas/firestore";
|
|
14
|
+
import type { JsonValue } from "../../../../schemas/types";
|
|
15
|
+
export interface RecordAdminActionInput {
|
|
16
|
+
actorUid: string;
|
|
17
|
+
actorName?: string;
|
|
18
|
+
action: AdminAuditAction;
|
|
19
|
+
targetType: string;
|
|
20
|
+
targetId: string;
|
|
21
|
+
targetLabel?: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
metadata?: Record<string, JsonValue>;
|
|
24
|
+
}
|
|
25
|
+
export declare function recordAdminAction(input: RecordAdminActionInput): Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recordAdminAction — the single write-site for the admin audit trail.
|
|
3
|
+
*
|
|
4
|
+
* MVP scope: instruments the highest-value already-privileged admin actions
|
|
5
|
+
* (bans, checkout bypass, coupon edits, payout mark-paid, store status
|
|
6
|
+
* changes, role changes) rather than sweeping every write path in the app —
|
|
7
|
+
* see CLAUDE.md's "Admin Audit Log" section for the full instrumented-action
|
|
8
|
+
* list and rationale.
|
|
9
|
+
*
|
|
10
|
+
* Callers should treat this as best-effort/non-blocking — a failure to write
|
|
11
|
+
* an audit entry must never fail the underlying privileged action itself.
|
|
12
|
+
*/
|
|
13
|
+
import { adminAuditLogRepository } from "../../../../features/audit-log/repository/audit-log.repository";
|
|
14
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
15
|
+
import { serverLogger } from "../../../../monitoring";
|
|
16
|
+
export async function recordAdminAction(input) {
|
|
17
|
+
try {
|
|
18
|
+
await adminAuditLogRepository.create({
|
|
19
|
+
actorUid: input.actorUid,
|
|
20
|
+
actorName: input.actorName,
|
|
21
|
+
action: input.action,
|
|
22
|
+
targetType: input.targetType,
|
|
23
|
+
targetId: input.targetId,
|
|
24
|
+
targetLabel: input.targetLabel,
|
|
25
|
+
reason: input.reason,
|
|
26
|
+
metadata: input.metadata,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const normalized = normalizeError(err);
|
|
31
|
+
serverLogger.warn("recordAdminAction failed (non-critical)", {
|
|
32
|
+
action: input.action,
|
|
33
|
+
targetType: input.targetType,
|
|
34
|
+
targetId: input.targetId,
|
|
35
|
+
error: normalized.message,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -26,8 +26,6 @@ function brandInputToCategoryFields(input) {
|
|
|
26
26
|
out.brandCountry = input.country;
|
|
27
27
|
if (input.founded !== undefined)
|
|
28
28
|
out.brandFounded = input.founded;
|
|
29
|
-
if (input.bannerURL !== undefined)
|
|
30
|
-
out.brandBannerImage = input.bannerURL;
|
|
31
29
|
if (input.logoURL !== undefined) {
|
|
32
30
|
out.display = { coverImage: input.logoURL, showInMenu: false, showInFooter: false };
|
|
33
31
|
}
|
|
@@ -7,7 +7,10 @@ export interface BrandOgData {
|
|
|
7
7
|
interface BrandDocLike {
|
|
8
8
|
name?: string | null;
|
|
9
9
|
description?: string | null;
|
|
10
|
-
logoURL
|
|
10
|
+
/** CategoryDocument has no `logoURL` field — the brand's image lives at `display.coverImage`, the same field the hero banner and generateMetadata's OG meta tag already use. */
|
|
11
|
+
display?: {
|
|
12
|
+
coverImage?: string | null;
|
|
13
|
+
} | null;
|
|
11
14
|
}
|
|
12
15
|
/** High-level OG renderer — accepts the raw brand document from `getBrandForDetail`. */
|
|
13
16
|
export declare function renderBrandOg(doc: BrandDocLike | null | undefined, opts: {
|
|
@@ -7,7 +7,7 @@ export function renderBrandOg(doc, opts) {
|
|
|
7
7
|
name,
|
|
8
8
|
description: doc?.description?.slice(0, 120) ??
|
|
9
9
|
`Shop authentic ${name} collectibles on ${opts.siteName}.`,
|
|
10
|
-
logoUrl: resolveOgImageUrl(doc?.
|
|
10
|
+
logoUrl: resolveOgImageUrl(doc?.display?.coverImage ?? null, opts.baseUrl),
|
|
11
11
|
}, opts.siteName);
|
|
12
12
|
}
|
|
13
13
|
export function renderBrandOgImage(data, siteName) {
|
|
@@ -30,6 +30,10 @@ export declare const getGroupsForProduct: (productId: string) => Promise<Grouped
|
|
|
30
30
|
* to render directly as a public GroupedListingsCarousel section.
|
|
31
31
|
*/
|
|
32
32
|
export declare const getGroupsWithItemsForProduct: (productId: string) => Promise<GroupedListingWithItems[]>;
|
|
33
|
+
/** Groups tagged to a category, hydrated with all member products — for the category detail page's GroupedListingsCarousel. */
|
|
34
|
+
export declare const getGroupsForCategory: (categorySlug: string) => Promise<GroupedListingWithItems[]>;
|
|
35
|
+
/** Groups tagged to a brand, hydrated with all member products — for the brand detail page's GroupedListingsCarousel. */
|
|
36
|
+
export declare const getGroupsForBrand: (brandSlug: string) => Promise<GroupedListingWithItems[]>;
|
|
33
37
|
export interface SitemapGroupedListing {
|
|
34
38
|
slug: string;
|
|
35
39
|
updatedAt: Date;
|
|
@@ -132,6 +132,30 @@ export const getGroupsWithItemsForProduct = cache(async (productId) => {
|
|
|
132
132
|
return { ...group, items: items.filter((p) => p !== null) };
|
|
133
133
|
}));
|
|
134
134
|
});
|
|
135
|
+
/** Groups tagged to a category, hydrated with all member products — for the category detail page's GroupedListingsCarousel. */
|
|
136
|
+
export const getGroupsForCategory = cache(async (categorySlug) => {
|
|
137
|
+
const groups = await groupedListingsRepository.findByCategorySlug(categorySlug).catch((err) => {
|
|
138
|
+
void normalizeError(err);
|
|
139
|
+
serverLogger.warn("grouped-data: getGroupsForCategory failed — returning empty", { categorySlug, error: err instanceof Error ? err.message : String(err) });
|
|
140
|
+
return [];
|
|
141
|
+
});
|
|
142
|
+
return Promise.all(groups.map(async (group) => {
|
|
143
|
+
const items = await Promise.all(group.productIds.map((id) => productRepository.findByIdOrSlug(id).catch(() => null)));
|
|
144
|
+
return { ...group, items: items.filter((p) => p !== null) };
|
|
145
|
+
}));
|
|
146
|
+
});
|
|
147
|
+
/** Groups tagged to a brand, hydrated with all member products — for the brand detail page's GroupedListingsCarousel. */
|
|
148
|
+
export const getGroupsForBrand = cache(async (brandSlug) => {
|
|
149
|
+
const groups = await groupedListingsRepository.findByBrandSlug(brandSlug).catch((err) => {
|
|
150
|
+
void normalizeError(err);
|
|
151
|
+
serverLogger.warn("grouped-data: getGroupsForBrand failed — returning empty", { brandSlug, error: err instanceof Error ? err.message : String(err) });
|
|
152
|
+
return [];
|
|
153
|
+
});
|
|
154
|
+
return Promise.all(groups.map(async (group) => {
|
|
155
|
+
const items = await Promise.all(group.productIds.map((id) => productRepository.findByIdOrSlug(id).catch(() => null)));
|
|
156
|
+
return { ...group, items: items.filter((p) => p !== null) };
|
|
157
|
+
}));
|
|
158
|
+
});
|
|
135
159
|
export async function listSitemapGroupedListings() {
|
|
136
160
|
try {
|
|
137
161
|
const db = getAdminDb();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, type GroupedListingWithItems, type ListGroupedListingsParams, type SitemapGroupedListing, } from "./data";
|
|
1
|
+
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, getGroupsForCategory, getGroupsForBrand, type GroupedListingWithItems, type ListGroupedListingsParams, type SitemapGroupedListing, } from "./data";
|
|
2
2
|
export { GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, } from "../../../shared/features/grouped/config";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, } from "./data";
|
|
1
|
+
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, getGroupsForCategory, getGroupsForBrand, } from "./data";
|
|
2
2
|
export { GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, } from "../../../shared/features/grouped/config";
|
|
@@ -161,6 +161,15 @@ export async function attachPaymentProofAction(orderId, proof) {
|
|
|
161
161
|
buyerFraudAgreementAcceptedAt: new Date(),
|
|
162
162
|
buyerReportedUpiId: proof.buyerReportedUpiId,
|
|
163
163
|
paymentUpiMismatch,
|
|
164
|
+
// A re-upload after `adminRequestProofReuploadAction` must clear the
|
|
165
|
+
// stale `reupload_requested` outcome. Both the 2-hour auto-approve
|
|
166
|
+
// sweep (`getUnreviewedProofPastDeadline`) and the admin
|
|
167
|
+
// "Awaiting verification" queue treat *any* set outcome as "already
|
|
168
|
+
// decided" — leaving it set meant a corrected proof was invisible to
|
|
169
|
+
// both: never auto-approved, never surfaced for manual review.
|
|
170
|
+
paymentReviewOutcome: null,
|
|
171
|
+
paymentReviewedBy: null,
|
|
172
|
+
paymentReviewedAt: null,
|
|
164
173
|
});
|
|
165
174
|
notifyAdminsOfPaymentProof({
|
|
166
175
|
id: orderId,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
function toIsoOrUndefined(value) {
|
|
2
|
+
return value instanceof Date ? value.toISOString() : undefined;
|
|
3
|
+
}
|
|
1
4
|
export function orderDocumentToOrder(doc) {
|
|
2
5
|
const items = doc.items?.length
|
|
3
6
|
? doc.items.map((item) => ({
|
|
@@ -55,10 +58,32 @@ export function orderDocumentToOrder(doc) {
|
|
|
55
58
|
couponCode: doc.couponCode,
|
|
56
59
|
trackingNumber: doc.trackingNumber,
|
|
57
60
|
shippingCarrier: doc.shippingCarrier,
|
|
61
|
+
trackingUrl: doc.trackingUrl,
|
|
58
62
|
notes: doc.notes,
|
|
63
|
+
orderDate: toIsoOrUndefined(doc.orderDate),
|
|
64
|
+
shippingDate: toIsoOrUndefined(doc.shippingDate),
|
|
65
|
+
deliveryDate: toIsoOrUndefined(doc.deliveryDate),
|
|
66
|
+
cancellationDate: toIsoOrUndefined(doc.cancellationDate),
|
|
59
67
|
autoApproved: doc.autoApproved,
|
|
60
68
|
disputeRaised: doc.disputeRaised,
|
|
61
69
|
disputeStatus: doc.disputeStatus,
|
|
70
|
+
// Manual-payment (cash / UPI / EMI) passthrough. These were previously
|
|
71
|
+
// dropped here, which silently disabled the entire buyer proof-upload
|
|
72
|
+
// flow: `/user/orders/[id]/payment` branches on `paymentMethod` and so
|
|
73
|
+
// rendered "this order does not require manual payment upload" for every
|
|
74
|
+
// order, with no UPI ID and no countdown.
|
|
75
|
+
paymentMethod: doc.paymentMethod,
|
|
76
|
+
displayedUpiId: doc.displayedUpiId,
|
|
77
|
+
paymentDeadline: toIsoOrUndefined(doc.paymentDeadline),
|
|
78
|
+
paymentProofUrl: doc.paymentProofUrl,
|
|
79
|
+
paymentProofUploadedAt: toIsoOrUndefined(doc.paymentProofUploadedAt),
|
|
80
|
+
paymentTransactionId: doc.paymentTransactionId,
|
|
81
|
+
buyerReportedUpiId: doc.buyerReportedUpiId,
|
|
82
|
+
buyerMarkedPaid: doc.buyerMarkedPaid,
|
|
83
|
+
paymentUpiMismatch: doc.paymentUpiMismatch,
|
|
84
|
+
paymentReviewOutcome: doc.paymentReviewOutcome,
|
|
85
|
+
paymentReviewNote: doc.paymentReviewNote,
|
|
86
|
+
cancellationReason: doc.cancellationReason,
|
|
62
87
|
createdAt: doc.createdAt instanceof Date
|
|
63
88
|
? doc.createdAt.toISOString()
|
|
64
89
|
: String(doc.createdAt),
|
|
@@ -7,6 +7,7 @@ export declare const couponExpiry: import("./types").ScheduledFunctionDefinition
|
|
|
7
7
|
export declare const offerExpiry: import("./types").ScheduledFunctionDefinition;
|
|
8
8
|
export declare const productStatsSync: import("./types").ScheduledFunctionDefinition;
|
|
9
9
|
export declare const revenueRollup: import("./types").ScheduledFunctionDefinition;
|
|
10
|
+
export declare const dailyStatusDigest: import("./types").ScheduledFunctionDefinition;
|
|
10
11
|
export declare const dailyDataCleanup: import("./types").ScheduledFunctionDefinition;
|
|
11
12
|
export declare const countersReconcile: import("./types").ScheduledFunctionDefinition;
|
|
12
13
|
export declare const positionsReconcile: import("./types").ScheduledFunctionDefinition;
|
|
@@ -19,8 +20,9 @@ export declare const cleanupRtdbEvents: import("./types").ScheduledFunctionDefin
|
|
|
19
20
|
export declare const mediaTmpCleanup: import("./types").ScheduledFunctionDefinition;
|
|
20
21
|
export declare const draftPrune: import("./types").ScheduledFunctionDefinition;
|
|
21
22
|
export declare const testerSandboxCleanup: import("./types").ScheduledFunctionDefinition;
|
|
23
|
+
export declare const testerSandboxRefresh: import("./types").ScheduledFunctionDefinition;
|
|
22
24
|
export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionDefinition;
|
|
23
25
|
export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
|
|
24
26
|
export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
|
|
25
27
|
export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
|
|
26
|
-
export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
|
|
28
|
+
export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Scheduled function definitions (Cloud Scheduler triggers).
|
|
2
2
|
// Cron syntax accepts both Firebase shorthand and standard cron expressions.
|
|
3
|
-
import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
|
|
3
|
+
import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, testerSandboxRefreshHandler, } from "../jobs/handlers";
|
|
4
4
|
import { defineFunction } from "./define";
|
|
5
5
|
const REGION = "asia-south1";
|
|
6
6
|
const EVERY_5_MIN = "every 5 minutes";
|
|
@@ -67,6 +67,13 @@ export const revenueRollup = defineFunction({
|
|
|
67
67
|
handler: revenueRollupHandler,
|
|
68
68
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
69
69
|
});
|
|
70
|
+
export const dailyStatusDigest = defineFunction({
|
|
71
|
+
name: "dailyStatusDigest",
|
|
72
|
+
description: "Email the previous 24h order summary + platform status to the team every morning at 10:00 IST.",
|
|
73
|
+
trigger: { kind: "schedule", cron: "0 10 * * *", timeZone: "Asia/Kolkata" },
|
|
74
|
+
handler: dailyStatusDigestHandler,
|
|
75
|
+
options: { region: REGION, timeoutSeconds: 120, memory: "256MiB", maxInstances: 1 },
|
|
76
|
+
});
|
|
70
77
|
export const dailyDataCleanup = defineFunction({
|
|
71
78
|
name: "dailyDataCleanup",
|
|
72
79
|
description: "Daily data cleanup (drafts, transient records) at 02:00 UTC.",
|
|
@@ -151,6 +158,13 @@ export const testerSandboxCleanup = defineFunction({
|
|
|
151
158
|
handler: testerSandboxCleanupHandler,
|
|
152
159
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
153
160
|
});
|
|
161
|
+
export const testerSandboxRefresh = defineFunction({
|
|
162
|
+
name: "testerSandboxRefresh",
|
|
163
|
+
description: "Revert live tester QA sandbox fixtures to their canonical seed shape and prune tester-created extras (every 4 hours).",
|
|
164
|
+
trigger: { kind: "schedule", cron: "0 */4 * * *", timeZone: "UTC" },
|
|
165
|
+
handler: testerSandboxRefreshHandler,
|
|
166
|
+
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
167
|
+
});
|
|
154
168
|
export const prizeDrawExpiryReveal = defineFunction({
|
|
155
169
|
name: "prizeDrawExpiryReveal",
|
|
156
170
|
description: "Assign winners for outstanding paid orders on scheduled-mode prize draws past their expiry, then close them (every 5 minutes).",
|
|
@@ -189,6 +203,7 @@ export const SCHEDULED_FUNCTIONS = [
|
|
|
189
203
|
offerExpiry,
|
|
190
204
|
productStatsSync,
|
|
191
205
|
revenueRollup,
|
|
206
|
+
dailyStatusDigest,
|
|
192
207
|
dailyDataCleanup,
|
|
193
208
|
countersReconcile,
|
|
194
209
|
positionsReconcile,
|
|
@@ -201,6 +216,7 @@ export const SCHEDULED_FUNCTIONS = [
|
|
|
201
216
|
mediaTmpCleanup,
|
|
202
217
|
draftPrune,
|
|
203
218
|
testerSandboxCleanup,
|
|
219
|
+
testerSandboxRefresh,
|
|
204
220
|
prizeDrawExpiryReveal,
|
|
205
221
|
bundleStockSync,
|
|
206
222
|
emiInstallmentReminder,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core: daily status digest email.
|
|
3
|
+
*
|
|
4
|
+
* Emails the previous 24h of order activity to the team every morning. The
|
|
5
|
+
* digest's arrival is itself the platform health signal — if it stops
|
|
6
|
+
* landing, something in the Firestore → Functions → Resend chain needs a
|
|
7
|
+
* look. Deliberately no separate error/exception tracking: there is no
|
|
8
|
+
* structured error-log collection to query, and building one just to fill
|
|
9
|
+
* this email would be scope no one asked for.
|
|
10
|
+
*
|
|
11
|
+
* Runs from a scheduled Firebase Function (not a Vercel route), so the
|
|
12
|
+
* unbounded-ish order scan below is safe against CLAUDE.md Rule #6's 10s
|
|
13
|
+
* sync ceiling — it has the Function's much larger budget.
|
|
14
|
+
*/
|
|
15
|
+
import type { JobContext } from "../runtime/types";
|
|
16
|
+
export interface DailyStatusDigestSummary {
|
|
17
|
+
orderCount: number;
|
|
18
|
+
revenue: number;
|
|
19
|
+
statusBreakdown: Record<string, number>;
|
|
20
|
+
stuckPendingCount: number;
|
|
21
|
+
activeListingCount: number;
|
|
22
|
+
}
|
|
23
|
+
/** Gathers the last-24h numbers. Exported so the admin manual-trigger route can reuse it. */
|
|
24
|
+
export declare function collectDailyStatusSummary(): Promise<DailyStatusDigestSummary>;
|
|
25
|
+
export interface DailyStatusDigestResult {
|
|
26
|
+
sent: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
summary?: DailyStatusDigestSummary;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sends the digest once per deployment version.
|
|
32
|
+
*
|
|
33
|
+
* Called from server startup. On Vercel that means EVERY lambda cold start,
|
|
34
|
+
* not once per deploy — so the version marker below is what makes this a
|
|
35
|
+
* per-deployment event rather than a per-cold-start email storm. The claim
|
|
36
|
+
* runs in a transaction because several cold starts can race on the first
|
|
37
|
+
* request after a deploy; exactly one wins and sends.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runDeploymentDigest(ctx: JobContext, version: string): Promise<DailyStatusDigestResult>;
|
|
40
|
+
export declare function runDailyStatusDigest(ctx: JobContext, version?: string): Promise<DailyStatusDigestResult>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Core: daily status digest email.
|
|
4
|
+
*
|
|
5
|
+
* Emails the previous 24h of order activity to the team every morning. The
|
|
6
|
+
* digest's arrival is itself the platform health signal — if it stops
|
|
7
|
+
* landing, something in the Firestore → Functions → Resend chain needs a
|
|
8
|
+
* look. Deliberately no separate error/exception tracking: there is no
|
|
9
|
+
* structured error-log collection to query, and building one just to fill
|
|
10
|
+
* this email would be scope no one asked for.
|
|
11
|
+
*
|
|
12
|
+
* Runs from a scheduled Firebase Function (not a Vercel route), so the
|
|
13
|
+
* unbounded-ish order scan below is safe against CLAUDE.md Rule #6's 10s
|
|
14
|
+
* sync ceiling — it has the Function's much larger budget.
|
|
15
|
+
*/
|
|
16
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
17
|
+
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
18
|
+
import { sendEmail } from "../../../../features/contact/email";
|
|
19
|
+
import { siteSettingsRepository } from "../../../../features/admin/repository/site-settings.repository";
|
|
20
|
+
import { ORDER_COLLECTION, OrderStatusValues } from "../../../../features/orders/schemas/firestore";
|
|
21
|
+
import { PRODUCT_COLLECTION, ProductStatusValues } from "../../../../features/products/schemas/firestore";
|
|
22
|
+
import { ORDER_FIELDS, PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
23
|
+
import { EmailBold, EmailContainer, EmailDivider, EmailDoc, EmailFooter, EmailHeader, EmailRow, } from "../../../../features/email/primitives";
|
|
24
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
25
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
/** Statuses that shouldn't count toward revenue for the day. */
|
|
27
|
+
const NON_REVENUE_STATUSES = new Set([
|
|
28
|
+
OrderStatusValues.CANCELLED,
|
|
29
|
+
OrderStatusValues.REFUNDED,
|
|
30
|
+
]);
|
|
31
|
+
function currency(amount) {
|
|
32
|
+
return `₹${amount.toLocaleString("en-IN", { maximumFractionDigits: 2 })}`;
|
|
33
|
+
}
|
|
34
|
+
/** One "Label: value" line. Uses only email primitives — email clients need
|
|
35
|
+
* inline-styled, table-based markup, which the Email* components provide. */
|
|
36
|
+
function StatLine({ label, value }) {
|
|
37
|
+
return (_jsxs(_Fragment, { children: [label, ": ", _jsx(EmailBold, { children: String(value) }), _jsx("br", {})] }));
|
|
38
|
+
}
|
|
39
|
+
/** Gathers the last-24h numbers. Exported so the admin manual-trigger route can reuse it. */
|
|
40
|
+
export async function collectDailyStatusSummary() {
|
|
41
|
+
const db = getAdminDb();
|
|
42
|
+
const since = new Date(Date.now() - DAY_MS);
|
|
43
|
+
const recentSnap = await db
|
|
44
|
+
.collection(ORDER_COLLECTION)
|
|
45
|
+
.where(ORDER_FIELDS.CREATED_AT, ">=", since)
|
|
46
|
+
.get();
|
|
47
|
+
let revenue = 0;
|
|
48
|
+
const statusBreakdown = {};
|
|
49
|
+
for (const doc of recentSnap.docs) {
|
|
50
|
+
const data = doc.data();
|
|
51
|
+
const status = data.status ?? "unknown";
|
|
52
|
+
statusBreakdown[status] = (statusBreakdown[status] ?? 0) + 1;
|
|
53
|
+
if (!NON_REVENUE_STATUSES.has(status)) {
|
|
54
|
+
revenue += Number(data.totalPrice ?? 0) || 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Orders still sitting in PENDING beyond the 24h window — the one number
|
|
58
|
+
// here that signals something needs attention rather than just reporting
|
|
59
|
+
// volume.
|
|
60
|
+
const stuckSnap = await db
|
|
61
|
+
.collection(ORDER_COLLECTION)
|
|
62
|
+
.where(ORDER_FIELDS.STATUS, "==", OrderStatusValues.PENDING)
|
|
63
|
+
.where(ORDER_FIELDS.CREATED_AT, "<", since)
|
|
64
|
+
.count()
|
|
65
|
+
.get();
|
|
66
|
+
const activeSnap = await db
|
|
67
|
+
.collection(PRODUCT_COLLECTION)
|
|
68
|
+
.where(PRODUCT_FIELDS.STATUS, "==", ProductStatusValues.PUBLISHED)
|
|
69
|
+
.count()
|
|
70
|
+
.get();
|
|
71
|
+
return {
|
|
72
|
+
orderCount: recentSnap.size,
|
|
73
|
+
revenue,
|
|
74
|
+
statusBreakdown,
|
|
75
|
+
stuckPendingCount: stuckSnap.data().count,
|
|
76
|
+
activeListingCount: activeSnap.data().count,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function renderDigestHtml(summary, siteName, version) {
|
|
80
|
+
const statusRows = Object.entries(summary.statusBreakdown).sort(([a], [b]) => a.localeCompare(b));
|
|
81
|
+
const title = siteName ? `${siteName} — Daily Status` : "Daily Status";
|
|
82
|
+
return `<!DOCTYPE html>${renderToStaticMarkup(_jsx(EmailDoc, { title: title, children: _jsxs(EmailContainer, { children: [_jsx(EmailHeader, { brandName: siteName }), _jsxs(EmailRow, { children: [version
|
|
83
|
+
? `Deployment ${version} is live. Activity for the last 24 hours.`
|
|
84
|
+
: "Activity for the last 24 hours.", _jsx("br", {}), _jsx("br", {}), _jsx(StatLine, { label: "Orders placed", value: summary.orderCount }), _jsx(StatLine, { label: "Revenue (excl. cancelled/refunded)", value: currency(summary.revenue) }), _jsx(StatLine, { label: "Active listings", value: summary.activeListingCount }), _jsx(StatLine, { label: "Pending > 24h (needs attention)", value: summary.stuckPendingCount })] }), _jsx(EmailDivider, {}), _jsxs(EmailRow, { children: [_jsx(EmailBold, { children: "Orders by status" }), _jsx("br", {}), _jsx("br", {}), statusRows.length === 0 ? ("No orders in this window.") : (statusRows.map(([status, count]) => (_jsx(StatLine, { label: status, value: count }, status))))] }), _jsx(EmailFooter, { copyright: `© ${new Date().getFullYear()}${siteName ? ` ${siteName}` : ""}. All rights reserved.` })] }) }))}`;
|
|
85
|
+
}
|
|
86
|
+
const DEPLOY_MARKER_COLLECTION = "system";
|
|
87
|
+
const DEPLOY_MARKER_DOC = "deployDigest";
|
|
88
|
+
/**
|
|
89
|
+
* Sends the digest once per deployment version.
|
|
90
|
+
*
|
|
91
|
+
* Called from server startup. On Vercel that means EVERY lambda cold start,
|
|
92
|
+
* not once per deploy — so the version marker below is what makes this a
|
|
93
|
+
* per-deployment event rather than a per-cold-start email storm. The claim
|
|
94
|
+
* runs in a transaction because several cold starts can race on the first
|
|
95
|
+
* request after a deploy; exactly one wins and sends.
|
|
96
|
+
*/
|
|
97
|
+
export async function runDeploymentDigest(ctx, version) {
|
|
98
|
+
if (!version)
|
|
99
|
+
return { sent: false, reason: "no_version" };
|
|
100
|
+
const markerRef = getAdminDb().collection(DEPLOY_MARKER_COLLECTION).doc(DEPLOY_MARKER_DOC);
|
|
101
|
+
let claimed = false;
|
|
102
|
+
try {
|
|
103
|
+
await getAdminDb().runTransaction(async (tx) => {
|
|
104
|
+
const snap = await tx.get(markerRef);
|
|
105
|
+
const lastVersion = snap.data()?.lastVersion;
|
|
106
|
+
if (lastVersion === version)
|
|
107
|
+
return;
|
|
108
|
+
tx.set(markerRef, { lastVersion: version, sentAt: new Date() }, { merge: true });
|
|
109
|
+
claimed = true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
void normalizeError(err);
|
|
114
|
+
ctx.logger.warn("deploymentDigest: could not claim version marker — skipping", { version });
|
|
115
|
+
return { sent: false, reason: "claim_failed" };
|
|
116
|
+
}
|
|
117
|
+
if (!claimed)
|
|
118
|
+
return { sent: false, reason: "already_sent_for_version" };
|
|
119
|
+
ctx.logger.info("deploymentDigest: claimed version, sending", { version });
|
|
120
|
+
return runDailyStatusDigest(ctx, version);
|
|
121
|
+
}
|
|
122
|
+
export async function runDailyStatusDigest(ctx, version) {
|
|
123
|
+
const settings = await siteSettingsRepository.getSingleton().catch((err) => {
|
|
124
|
+
void normalizeError(err);
|
|
125
|
+
return null;
|
|
126
|
+
});
|
|
127
|
+
const digestConfig = settings?.emailSettings?.dailyDigest;
|
|
128
|
+
if (digestConfig?.enabled !== true) {
|
|
129
|
+
ctx.logger.info("dailyStatusDigest: disabled in site settings — skipping");
|
|
130
|
+
return { sent: false, reason: "disabled" };
|
|
131
|
+
}
|
|
132
|
+
const recipients = (digestConfig.recipients ?? []).filter((r) => r.trim().length > 0);
|
|
133
|
+
if (recipients.length === 0) {
|
|
134
|
+
ctx.logger.warn("dailyStatusDigest: enabled but no recipients configured — skipping");
|
|
135
|
+
return { sent: false, reason: "no_recipients" };
|
|
136
|
+
}
|
|
137
|
+
const ccRecipients = (digestConfig.ccRecipients ?? []).filter((r) => r.trim().length > 0);
|
|
138
|
+
const summary = await collectDailyStatusSummary();
|
|
139
|
+
// No hardcoded brand fallback — appkit is consumer-agnostic; the site name
|
|
140
|
+
// comes from settings or the consumer's own env var.
|
|
141
|
+
const siteName = settings?.siteName?.trim() || process.env.NEXT_PUBLIC_SITE_NAME?.trim() || "";
|
|
142
|
+
const { error } = await sendEmail({
|
|
143
|
+
to: recipients,
|
|
144
|
+
// Spread rather than `cc: undefined` — SendEmailOptions has a
|
|
145
|
+
// Record<string, JsonValue> index signature that rejects undefined.
|
|
146
|
+
...(ccRecipients.length > 0 ? { cc: ccRecipients } : {}),
|
|
147
|
+
subject: "Daily Status",
|
|
148
|
+
html: renderDigestHtml(summary, siteName, version),
|
|
149
|
+
text: (version ? `Deployment ${version} is live.\n\n` : "") +
|
|
150
|
+
`Orders placed: ${summary.orderCount}\n` +
|
|
151
|
+
`Revenue: ${currency(summary.revenue)}\n` +
|
|
152
|
+
`Active listings: ${summary.activeListingCount}\n` +
|
|
153
|
+
`Pending > 24h: ${summary.stuckPendingCount}`,
|
|
154
|
+
});
|
|
155
|
+
if (error) {
|
|
156
|
+
ctx.logger.error("dailyStatusDigest: send failed", { error: String(error) });
|
|
157
|
+
throw new Error(`Daily status digest send failed: ${String(error)}`);
|
|
158
|
+
}
|
|
159
|
+
ctx.logger.info("dailyStatusDigest: sent", {
|
|
160
|
+
recipients: recipients.length,
|
|
161
|
+
orderCount: summary.orderCount,
|
|
162
|
+
});
|
|
163
|
+
return { sent: true, summary };
|
|
164
|
+
}
|