@mohasinac/appkit 3.6.5 → 3.8.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 +17 -3
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +7 -2
- package/dist/_internal/client/features/tour/TourProvider.d.ts +5 -2
- package/dist/_internal/client/features/tour/TourProvider.js +125 -12
- package/dist/_internal/client/theme/ThemeProvider.js +1 -1
- package/dist/_internal/server/features/categories/data.js +2 -2
- package/dist/_internal/server/features/faqs/og.js +5 -1
- package/dist/_internal/server/features/reviews/og.js +5 -1
- package/dist/_internal/server/features/seo/manifest.js +2 -1
- package/dist/_internal/server/features/seo/og.js +7 -1
- package/dist/_internal/server/features/tester/visibility.d.ts +13 -0
- package/dist/_internal/server/features/tester/visibility.js +28 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +9 -1
- package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
- package/dist/_internal/server/jobs/core/resetOtpVerification.d.ts +17 -0
- package/dist/_internal/server/jobs/core/resetOtpVerification.js +88 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +6 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +35 -0
- package/dist/_internal/server/jobs/handlers/_helpers.d.ts +6 -0
- package/dist/_internal/server/jobs/handlers/_helpers.js +12 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +2 -0
- package/dist/_internal/server/jobs/handlers/messages.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/messages.js +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +56 -0
- package/dist/_internal/shared/tokens/index.d.ts +12 -12
- package/dist/client.d.ts +15 -3
- package/dist/client.js +10 -2
- package/dist/constants/api-endpoints.d.ts +39 -12
- package/dist/constants/api-endpoints.js +17 -8
- package/dist/constants/field-names.d.ts +9 -0
- package/dist/constants/field-names.js +12 -0
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/features/account/components/LinkedAccountsSection.d.ts +11 -0
- package/dist/features/account/components/LinkedAccountsSection.js +11 -0
- package/dist/features/account/components/UserSidebar.d.ts +3 -1
- package/dist/features/account/components/UserSidebar.js +9 -13
- package/dist/features/account/hooks/useCollapsedSections.d.ts +20 -0
- package/dist/features/account/hooks/useCollapsedSections.js +63 -0
- package/dist/features/account/hooks/useProfile.d.ts +8 -0
- package/dist/features/account/index.d.ts +1 -0
- package/dist/features/account/index.js +1 -0
- package/dist/features/admin/components/AdminAddressEditorView.js +2 -2
- package/dist/features/admin/components/AdminBidsView.js +6 -6
- package/dist/features/admin/components/AdminBrandsView.js +2 -2
- package/dist/features/admin/components/AdminCarouselView.js +2 -2
- package/dist/features/admin/components/AdminCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
- package/dist/features/admin/components/AdminProductEditorView.js +5 -2
- package/dist/features/admin/components/AdminReviewsView.js +5 -5
- package/dist/features/admin/components/AdminSectionsView.js +3 -18
- package/dist/features/admin/components/AdminSidebar.js +8 -6
- package/dist/features/admin/components/AdminSiteSettingsView.js +133 -127
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +1 -1
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.d.ts +9 -0
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +129 -0
- package/dist/features/admin/components/AdminTesterChecklistView.d.ts +6 -0
- package/dist/features/admin/components/AdminTesterChecklistView.js +93 -0
- package/dist/features/admin/components/AdminUserEditorView.d.ts +3 -1
- package/dist/features/admin/components/AdminUserEditorView.js +7 -1
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/DataListingView.js +2 -2
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +8 -0
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +41 -0
- package/dist/features/admin/components/index.d.ts +6 -0
- package/dist/features/admin/components/index.js +3 -0
- package/dist/features/admin/schemas/firestore.d.ts +0 -7
- package/dist/features/admin/schemas/firestore.js +6 -6
- package/dist/features/analytics/components/PageViewTracker.d.ts +12 -0
- package/dist/features/analytics/components/PageViewTracker.js +22 -0
- package/dist/features/analytics/repository/page-views.repository.d.ts +32 -0
- package/dist/features/analytics/repository/page-views.repository.js +43 -0
- package/dist/features/analytics/types.d.ts +6 -0
- package/dist/features/analytics/types.js +13 -0
- package/dist/features/auth/components/SocialAuthButtons.js +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts +12 -0
- package/dist/features/auth/hooks/useAuth.js +109 -0
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/repository/session.repository.d.ts +2 -0
- package/dist/features/auth/repository/session.repository.js +4 -1
- package/dist/features/auth/schemas/firestore.d.ts +17 -1
- package/dist/features/auth/schemas/firestore.js +2 -0
- package/dist/features/blog/components/BlogIndexListing.js +2 -2
- package/dist/features/blog/schemas/firestore.d.ts +4 -0
- package/dist/features/cart/actions/cart-actions.js +20 -2
- package/dist/features/categories/api/route.js +2 -2
- package/dist/features/categories/components/CategoriesIndexListing.js +3 -3
- package/dist/features/categories/components/CategoryBundlesListing.js +2 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +3 -1
- package/dist/features/categories/components/CategoryProductsListing.js +2 -2
- package/dist/features/categories/schemas/firestore.d.ts +4 -0
- package/dist/features/classified/components/ClassifiedIndexListing.js +2 -2
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +2 -2
- package/dist/features/email/primitives.d.ts +2 -2
- package/dist/features/email/primitives.js +2 -2
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/repository/event-entry.repository.js +1 -0
- package/dist/features/events/schemas/firestore.d.ts +7 -1
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/layout/BackToTop.d.ts +6 -1
- package/dist/features/layout/BackToTop.js +31 -6
- package/dist/features/layout/BottomNavbar.js +1 -1
- package/dist/features/layout/NavItem.js +1 -1
- package/dist/features/layout/NavbarLayout.js +2 -2
- package/dist/features/live/components/LiveItemsIndexListing.js +2 -2
- package/dist/features/media/MediaVideo.js +5 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -2
- package/dist/features/products/components/AuctionsIndexListing.js +2 -2
- package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -2
- package/dist/features/products/components/ProductsIndexListing.js +2 -2
- package/dist/features/products/constants/action-defs.d.ts +2 -0
- package/dist/features/products/constants/action-defs.js +2 -0
- package/dist/features/products/constants/sieve.d.ts +0 -6
- package/dist/features/products/constants/sieve.js +4 -2
- package/dist/features/products/repository/products.repository.d.ts +20 -0
- package/dist/features/products/repository/products.repository.js +5 -0
- package/dist/features/products/schemas/firestore.d.ts +4 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +18 -5
- package/dist/features/reviews/components/ReviewsIndexListing.js +2 -2
- package/dist/features/search/hooks/useNavSuggestions.js +20 -1
- package/dist/features/seller/actions/seller-actions.js +11 -6
- package/dist/features/seller/components/SellerAddressesView.js +2 -2
- package/dist/features/seller/components/SellerBidsView.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerOrdersView.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +2 -2
- package/dist/features/seller/components/SellerSidebar.d.ts +3 -1
- package/dist/features/seller/components/SellerSidebar.js +9 -8
- package/dist/features/seller/components/SellerStoreCategoriesView.js +2 -2
- package/dist/features/seller/components/SellerTemplatesView.js +2 -2
- package/dist/features/shell/StepForm.js +4 -4
- package/dist/features/stores/actions/store-query-actions.d.ts +5 -4
- package/dist/features/stores/actions/store-query-actions.js +11 -8
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -2
- package/dist/features/stores/components/StoreClassifiedsListing.js +2 -2
- package/dist/features/stores/components/StoreDetailLayoutView.js +1 -1
- package/dist/features/stores/components/StoreDigitalCodesListing.js +2 -2
- package/dist/features/stores/components/StoreLiveItemsListing.js +2 -2
- package/dist/features/stores/components/StorePreOrdersListing.js +2 -2
- package/dist/features/stores/components/StoreProductsListing.js +2 -2
- package/dist/features/stores/components/StoreReviewsListing.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +2 -2
- package/dist/features/stores/repository/store.repository.js +3 -0
- package/dist/features/stores/schemas/firestore.d.ts +4 -0
- package/dist/features/tester/actions/checklist-item-actions.d.ts +69 -0
- package/dist/features/tester/actions/checklist-item-actions.js +42 -0
- package/dist/features/tester/actions/index.d.ts +1 -0
- package/dist/features/tester/actions/index.js +1 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.d.ts +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +22 -0
- package/dist/features/tester/components/AdminTesterFeedbackListView.d.ts +3 -0
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +100 -0
- package/dist/features/tester/components/AdminTesterFeedbackReportView.d.ts +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackReportView.js +30 -0
- package/dist/features/tester/components/AdminTesterFeedbackView.d.ts +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackView.js +29 -0
- package/dist/features/tester/components/TesterChecklistStepRow.d.ts +18 -0
- package/dist/features/tester/components/TesterChecklistStepRow.js +29 -0
- package/dist/features/tester/components/TesterFeedbackChart.d.ts +14 -0
- package/dist/features/tester/components/TesterFeedbackChart.js +18 -0
- package/dist/features/tester/components/TesterHubView.d.ts +5 -0
- package/dist/features/tester/components/TesterHubView.js +71 -0
- package/dist/features/tester/components/index.d.ts +11 -0
- package/dist/features/tester/components/index.js +7 -0
- package/dist/features/tester/index.d.ts +2 -0
- package/dist/features/tester/index.js +2 -0
- package/dist/features/tester/repository/index.d.ts +2 -0
- package/dist/features/tester/repository/index.js +2 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +13 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.js +55 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +50 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.js +196 -0
- package/dist/features/tester/schemas/firestore.d.ts +85 -0
- package/dist/features/tester/schemas/firestore.js +93 -0
- package/dist/features/tester/schemas/index.d.ts +1 -0
- package/dist/features/tester/schemas/index.js +1 -0
- package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/blog-tester-seed-data.js +40 -0
- package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +177 -0
- package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/events-tester-seed-data.js +50 -0
- package/dist/features/tester/seed-data/index.d.ts +6 -0
- package/dist/features/tester/seed-data/index.js +6 -0
- package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/products-tester-seed-data.js +151 -0
- package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +40 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +248 -0
- package/dist/features/tester/seed-data/tester-ttl.d.ts +4 -0
- package/dist/features/tester/seed-data/tester-ttl.js +6 -0
- package/dist/features/tester/server.d.ts +9 -0
- package/dist/features/tester/server.js +8 -0
- package/dist/index.d.ts +28 -2
- package/dist/index.js +51 -1
- package/dist/next/api/api-response.js +5 -1
- package/dist/next/api/routeHandler.js +10 -1
- package/dist/next/middleware/chain.js +1 -1
- package/dist/next/routing/route-map.d.ts +6 -7
- package/dist/next/routing/route-map.js +3 -7
- package/dist/providers/payment-manual/index.d.ts +2 -2
- package/dist/providers/payment-manual/index.js +2 -2
- package/dist/react/contexts/SessionContext.d.ts +4 -0
- package/dist/react/contexts/SessionContext.js +4 -0
- package/dist/repositories/index.d.ts +4 -0
- package/dist/repositories/index.js +3 -0
- package/dist/security/pii-encrypt.js +12 -2
- package/dist/seed/addresses-seed-data.js +1 -1
- package/dist/seed/bids-seed-data.js +64 -185
- package/dist/seed/blog-posts-seed-data.js +1 -1
- package/dist/seed/carousel-slides-seed-data.js +22 -16
- package/dist/seed/carousels-seed-data.js +1 -1
- package/dist/seed/cart-seed-data.js +1 -1
- package/dist/seed/categories-seed-data.js +1 -1
- package/dist/seed/claimed-coupons-seed-data.js +1 -1
- package/dist/seed/conversations-seed-data.js +1 -1
- package/dist/seed/coupon-usage-seed-data.js +1 -1
- package/dist/seed/coupons-seed-data.js +1 -1
- package/dist/seed/events-seed-data.js +1 -1
- package/dist/seed/faq-seed-data.js +119 -3
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
- package/dist/seed/grouped-listings-seed-data.js +1 -1
- package/dist/seed/history-seed-data.js +1 -1
- package/dist/seed/homepage-sections-seed-data.js +1 -1
- package/dist/seed/index.d.ts +2 -2
- package/dist/seed/index.js +7 -3
- package/dist/seed/lottery-entries-seed-data.js +1 -1
- package/dist/seed/manifest.d.ts +4 -3
- package/dist/seed/manifest.js +13 -6
- package/dist/seed/notifications-seed-data.js +1 -1
- package/dist/seed/offers-seed-data.js +1 -1
- package/dist/seed/orders-seed-data.js +1 -1
- package/dist/seed/payouts-seed-data.js +1 -1
- package/dist/seed/products-art-seed-data.js +1 -1
- package/dist/seed/products-auctions-seed-data.js +1 -1
- package/dist/seed/products-classifieds-seed-data.js +1 -1
- package/dist/seed/products-digital-codes-seed-data.js +1 -1
- package/dist/seed/products-live-items-seed-data.js +1 -1
- package/dist/seed/products-preorders-seed-data.d.ts +2 -0
- package/dist/seed/products-preorders-seed-data.js +1 -1
- package/dist/seed/products-prize-draws-seed-data.js +1 -1
- package/dist/seed/products-standard-seed-data.js +1 -1
- package/dist/seed/products-stickers-seed-data.js +1 -1
- package/dist/seed/reviews-seed-data.js +1 -1
- package/dist/seed/scammers-seed-data.js +1 -1
- package/dist/seed/sessions-seed-data.js +1 -1
- package/dist/seed/site-settings-seed-data.js +22 -16
- package/dist/seed/store-addresses-seed-data.js +1 -1
- package/dist/seed/store-extensions-seed-data.js +1 -1
- package/dist/seed/stores-seed-data.js +1 -1
- package/dist/seed/support-tickets-seed-data.js +1 -1
- package/dist/seed/tester-checklist-seed-data.d.ts +2 -0
- package/dist/seed/tester-checklist-seed-data.js +248 -0
- package/dist/seed/users-seed-data.js +1 -1
- package/dist/seed/wishlists-seed-data.js +1 -1
- package/dist/server.d.ts +7 -2
- package/dist/server.js +19 -12
- package/dist/styles.css +181 -165
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/color-pairs.d.ts +46 -0
- package/dist/tokens/color-pairs.js +93 -0
- package/dist/tokens/index.d.ts +60 -60
- package/dist/tokens/index.js +30 -30
- package/dist/tokens/themes/cobalt-night.d.ts +3 -2
- package/dist/tokens/themes/cobalt-night.js +3 -2
- package/dist/tokens/themes/default-dark.d.ts +4 -1
- package/dist/tokens/themes/default-dark.js +53 -50
- package/dist/tokens/themes/default-light.d.ts +5 -1
- package/dist/tokens/themes/default-light.js +48 -44
- package/dist/tokens/themes/sunset.js +4 -4
- package/dist/tokens/tokens.css +128 -122
- package/dist/ui/components/CollapsibleSection.d.ts +15 -0
- package/dist/ui/components/CollapsibleSection.js +13 -0
- package/dist/ui/components/ListingToolbar.js +3 -2
- package/dist/ui/components/Semantic.style.css +3 -3
- package/dist/ui/components/SideDrawer.style.css +19 -19
- package/dist/ui/components/Slider.js +1 -1
- package/dist/ui/components/StickyToolbar.d.ts +36 -7
- package/dist/ui/components/StickyToolbar.js +63 -5
- package/dist/ui/components/Tabs.js +17 -1
- package/dist/ui/components/Tabs.style.css +10 -0
- package/dist/ui/components/Typography.js +7 -0
- package/dist/ui/components/surface-tokens.d.ts +0 -7
- package/dist/ui/components/surface-tokens.js +7 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/rich-text/RichText.style.css +20 -20
- package/dist/utils/id-generators.d.ts +13 -0
- package/dist/utils/id-generators.js +17 -0
- package/package.json +1 -1
- package/scripts/seed-cli-loader.mjs +15 -0
- package/scripts/seed-cli.mjs +71 -17
|
@@ -37,8 +37,22 @@ export interface DashboardLayoutClientProps {
|
|
|
37
37
|
activeHref?: string;
|
|
38
38
|
/** Responsive controls — currently only hideAt is honoured. */
|
|
39
39
|
responsive?: SectionResponsive;
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Cross-dashboard nav links shown in the sidebar footer. Plain hrefs
|
|
42
|
+
* rather than a render-prop — a Server Component caller (every real
|
|
43
|
+
* consumer: admin/store/user layout.tsx) cannot legally pass a function
|
|
44
|
+
* across the RSC boundary to this "use client" component, so the
|
|
45
|
+
* previous `renderSidebarFooter?: () => ReactNode` shape was never
|
|
46
|
+
* actually usable from where it needed to be used.
|
|
47
|
+
*/
|
|
48
|
+
crossNav?: {
|
|
49
|
+
/** Always-available "My Profile" shortcut — every dashboard user is also a user. */
|
|
50
|
+
profileHref?: string;
|
|
51
|
+
/** Admin only: shown when the signed-in admin also owns a store. */
|
|
52
|
+
storeHref?: string;
|
|
53
|
+
/** Seller only: shown when the signed-in seller account is also an admin/employee. */
|
|
54
|
+
adminHref?: string;
|
|
55
|
+
};
|
|
42
56
|
/** Optional className passed through to the sidebar component. */
|
|
43
57
|
className?: string;
|
|
44
58
|
/** Override the content area padding classes. */
|
|
@@ -53,4 +67,4 @@ export interface DashboardLayoutClientProps {
|
|
|
53
67
|
darkBackground?: BackgroundConfig;
|
|
54
68
|
children: ReactNode;
|
|
55
69
|
}
|
|
56
|
-
export declare function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }: DashboardLayoutClientProps): import("react").JSX.Element;
|
|
70
|
+
export declare function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, crossNav, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }: DashboardLayoutClientProps): import("react").JSX.Element;
|
|
@@ -147,7 +147,7 @@ function DashboardBottomNav({ items, activeHref }) {
|
|
|
147
147
|
}
|
|
148
148
|
const DEFAULT_CONTENT_PADDING = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pl-14 lg:pr-6 xl:pl-16 xl:pr-10";
|
|
149
149
|
const DEFAULT_CONTENT_MAX_WIDTH = "max-w-screen-2xl";
|
|
150
|
-
export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
|
|
150
|
+
export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, crossNav, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
|
|
151
151
|
const pathname = usePathname();
|
|
152
152
|
const activeHref = explicitActiveHref ?? pathname ?? "";
|
|
153
153
|
const storageKey = `appkit:sidebar-open:${variant}`;
|
|
@@ -165,7 +165,12 @@ export function DashboardLayoutClient({ variant, groups, permissions, activeHref
|
|
|
165
165
|
const adminGroups = variant === "admin"
|
|
166
166
|
? filteredGroups
|
|
167
167
|
: groups;
|
|
168
|
-
|
|
168
|
+
const crossNavLinkClass = "flex items-center gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-[var(--appkit-color-text)] transition-colors";
|
|
169
|
+
const hasCrossNav = Boolean(crossNav?.profileHref || crossNav?.storeHref || crossNav?.adminHref);
|
|
170
|
+
const renderCrossNavFooter = hasCrossNav
|
|
171
|
+
? () => (_jsxs(Nav, { "aria-label": "Cross-dashboard links", padding: "y-xs", children: [crossNav?.storeHref && (_jsx(Link, { href: crossNav.storeHref, onClick: closeMobile, className: crossNavLinkClass, children: "Go to my Store" })), crossNav?.adminHref && (_jsx(Link, { href: crossNav.adminHref, onClick: closeMobile, className: crossNavLinkClass, children: "Back to Admin" })), crossNav?.profileHref && (_jsx(Link, { href: crossNav.profileHref, onClick: closeMobile, className: crossNavLinkClass, children: "My Profile" }))] }))
|
|
172
|
+
: undefined;
|
|
173
|
+
return (_jsxs(_Fragment, { children: [hasBackground && (_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: resolvedLightBackground ?? { type: "color", value: "" }, darkMode: resolvedDarkBackground ?? { type: "color", value: "" } })), variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: adminGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: filteredGroups.flatMap((g) => g.items), groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), _jsx(Div, { className: [
|
|
169
174
|
"w-full flex-1 flex flex-col min-h-[calc(100dvh-var(--header-height,3.5rem))]",
|
|
170
175
|
"pb-[var(--appkit-space-16)] lg:pb-[0]", // clears the fixed mobile bottom-nav
|
|
171
176
|
contentPadding ?? DEFAULT_CONTENT_PADDING,
|
|
@@ -2,8 +2,11 @@ import React from "react";
|
|
|
2
2
|
import type { DriveStep } from "driver.js";
|
|
3
3
|
export type TourRole = "buyer" | "seller" | "admin";
|
|
4
4
|
export interface TourContextValue {
|
|
5
|
-
/** Starts the tour for the given role (defaults to "buyer")
|
|
6
|
-
|
|
5
|
+
/** Starts the tour for the given role (defaults to "buyer") at the given
|
|
6
|
+
* pathname (defaults to "/" — the full role-based tour). Passing the
|
|
7
|
+
* current route lets the tour show only the steps relevant to what's
|
|
8
|
+
* actually on screen, when that route has a registered contextual set. */
|
|
9
|
+
startTour: (role?: TourRole, pathname?: string) => void;
|
|
7
10
|
}
|
|
8
11
|
export declare function useTour(): TourContextValue;
|
|
9
12
|
export declare const CUSTOMER_TOUR_STEPS: DriveStep[];
|
|
@@ -12,16 +12,32 @@ const step = (element, title, description) => ({
|
|
|
12
12
|
popover: { title, description },
|
|
13
13
|
skipMissingElement: true,
|
|
14
14
|
});
|
|
15
|
-
// P-16 —
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
15
|
+
// P-16 — Anchors are `data-tour="…"` attributes on TitleBarLayout's icon
|
|
16
|
+
// buttons (present on every page) plus a couple of dashboard-specific
|
|
17
|
+
// anchors. `skipMissingElement: true` on every step means a step whose
|
|
18
|
+
// target isn't on the current page is silently skipped rather than
|
|
19
|
+
// breaking the tour.
|
|
20
|
+
//
|
|
21
|
+
// Redesigned (Phase 16) from one fixed step list per role into: a short
|
|
22
|
+
// universal nav orientation (same on every page, since the header anchors
|
|
23
|
+
// are always present) + a per-route contextual slice keyed off the current
|
|
24
|
+
// pathname. A route with no registered slice falls back to that role's full
|
|
25
|
+
// legacy step list, so nothing regresses for pages not yet given contextual
|
|
26
|
+
// content.
|
|
27
|
+
const UNIVERSAL_NAV_STEPS = {
|
|
28
|
+
buyer: [
|
|
29
|
+
step('[data-tour="nav-search"]', "Search", "Search by name, brand, or price to find what you're after."),
|
|
30
|
+
step('[data-tour="nav-wishlist"]', "Wishlist", "Save items here to come back to them later."),
|
|
31
|
+
step('[data-tour="nav-cart"]', "Cart", "Review your items and check out when you're ready."),
|
|
32
|
+
step('[data-tour="nav-profile"]', "Your account", "Manage your profile, orders, and messages here."),
|
|
33
|
+
],
|
|
34
|
+
seller: [
|
|
35
|
+
step('[data-tour="nav-profile"]', "Store settings", "Set up your store profile, shipping, and pickup address."),
|
|
36
|
+
],
|
|
37
|
+
admin: [],
|
|
38
|
+
};
|
|
20
39
|
export const CUSTOMER_TOUR_STEPS = [
|
|
21
|
-
|
|
22
|
-
step('[data-tour="nav-wishlist"]', "Wishlist", "Save items here to come back to them later."),
|
|
23
|
-
step('[data-tour="nav-cart"]', "Cart", "Review your items and check out when you're ready."),
|
|
24
|
-
step('[data-tour="nav-profile"]', "Your account", "Manage your profile, orders, and messages here."),
|
|
40
|
+
...UNIVERSAL_NAV_STEPS.buyer,
|
|
25
41
|
{
|
|
26
42
|
popover: {
|
|
27
43
|
title: "Checkout",
|
|
@@ -54,7 +70,7 @@ export const SELLER_TOUR_STEPS = [
|
|
|
54
70
|
},
|
|
55
71
|
skipMissingElement: true,
|
|
56
72
|
},
|
|
57
|
-
|
|
73
|
+
...UNIVERSAL_NAV_STEPS.seller,
|
|
58
74
|
step('[data-tour="store-analytics"]', "Analytics", "Track your sales, revenue, and top products."),
|
|
59
75
|
step('[data-tour="store-payouts"]', "Payouts", "See what you've earned and your payout history."),
|
|
60
76
|
];
|
|
@@ -79,6 +95,103 @@ const STEP_SETS = {
|
|
|
79
95
|
seller: SELLER_TOUR_STEPS,
|
|
80
96
|
admin: ADMIN_TOUR_STEPS,
|
|
81
97
|
};
|
|
98
|
+
// One entry per page that has dedicated data-tour anchors — the contextual
|
|
99
|
+
// slice for that exact page, not the whole role's tour.
|
|
100
|
+
const ROUTE_TOUR_STEPS = [
|
|
101
|
+
{
|
|
102
|
+
pathPrefix: "/store/products",
|
|
103
|
+
role: "seller",
|
|
104
|
+
steps: [
|
|
105
|
+
step('[data-tour="store-products"]', "Your listings", "Create and manage your product listings here."),
|
|
106
|
+
{
|
|
107
|
+
popover: {
|
|
108
|
+
title: "Product form",
|
|
109
|
+
description: "Fill in details, images, and pricing — you can save as a draft anytime.",
|
|
110
|
+
},
|
|
111
|
+
skipMissingElement: true,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
pathPrefix: "/store/orders",
|
|
117
|
+
role: "seller",
|
|
118
|
+
steps: [
|
|
119
|
+
step('[data-tour="store-orders"]', "Incoming orders", "See and manage orders as buyers place them."),
|
|
120
|
+
{
|
|
121
|
+
popover: {
|
|
122
|
+
title: "Mark as shipped",
|
|
123
|
+
description: "Open an order to enter the carrier name and tracking number once it ships.",
|
|
124
|
+
},
|
|
125
|
+
skipMissingElement: true,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
pathPrefix: "/store/analytics",
|
|
131
|
+
role: "seller",
|
|
132
|
+
steps: [step('[data-tour="store-analytics"]', "Analytics", "Track your sales, revenue, and top products.")],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
pathPrefix: "/store/payouts",
|
|
136
|
+
role: "seller",
|
|
137
|
+
steps: [step('[data-tour="store-payouts"]', "Payouts", "See what you've earned and your payout history.")],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
pathPrefix: "/admin/orders",
|
|
141
|
+
role: "admin",
|
|
142
|
+
steps: [
|
|
143
|
+
step('[data-tour="admin-orders"]', "Marketplace orders", "All orders across every store, in one place."),
|
|
144
|
+
{
|
|
145
|
+
popover: {
|
|
146
|
+
title: "Verify payments",
|
|
147
|
+
description: "Open an order to confirm manual payments and update its status.",
|
|
148
|
+
},
|
|
149
|
+
skipMissingElement: true,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
pathPrefix: "/admin/products",
|
|
155
|
+
role: "admin",
|
|
156
|
+
steps: [step('[data-tour="admin-products"]', "Moderation", "Approve or reject listings before they go live.")],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
pathPrefix: "/admin/users",
|
|
160
|
+
role: "admin",
|
|
161
|
+
steps: [step('[data-tour="admin-users"]', "Users", "Manage buyer and seller accounts.")],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
pathPrefix: "/admin/stores",
|
|
165
|
+
role: "admin",
|
|
166
|
+
steps: [step('[data-tour="admin-stores"]', "Stores", "Approve new store applications.")],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
pathPrefix: "/admin/analytics",
|
|
170
|
+
role: "admin",
|
|
171
|
+
steps: [step('[data-tour="admin-analytics"]', "Analytics", "Platform-wide GMV, orders, and top products.")],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
pathPrefix: "/admin/site",
|
|
175
|
+
role: "admin",
|
|
176
|
+
steps: [step('[data-tour="admin-settings"]', "Site settings", "Configure how the platform behaves.")],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
pathPrefix: "/admin/feature-flags",
|
|
180
|
+
role: "admin",
|
|
181
|
+
steps: [step('[data-tour="admin-feature-flags"]', "Feature flags", "Turn platform features on or off.")],
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
/** Strips a leading two-letter locale segment ("/en/store/products" → "/store/products"). */
|
|
185
|
+
function stripLocale(pathname) {
|
|
186
|
+
return pathname.replace(/^\/[a-z]{2}(?=\/|$)/, "") || "/";
|
|
187
|
+
}
|
|
188
|
+
function resolveSteps(role, pathname) {
|
|
189
|
+
const stripped = stripLocale(pathname);
|
|
190
|
+
const match = ROUTE_TOUR_STEPS.find((entry) => entry.role === role && stripped.startsWith(entry.pathPrefix));
|
|
191
|
+
if (!match)
|
|
192
|
+
return STEP_SETS[role];
|
|
193
|
+
return [...UNIVERSAL_NAV_STEPS[role], ...match.steps];
|
|
194
|
+
}
|
|
82
195
|
/**
|
|
83
196
|
* TourProvider — lazily imports driver.js on first use so it never lands in
|
|
84
197
|
* the initial bundle. Respects prefers-reduced-motion by disabling driver.js's
|
|
@@ -86,7 +199,7 @@ const STEP_SETS = {
|
|
|
86
199
|
* works; only the animated transition is skipped).
|
|
87
200
|
*/
|
|
88
201
|
export function TourProvider({ children }) {
|
|
89
|
-
const startTour = React.useCallback((role = "buyer") => {
|
|
202
|
+
const startTour = React.useCallback((role = "buyer", pathname = "/") => {
|
|
90
203
|
if (typeof window === "undefined")
|
|
91
204
|
return;
|
|
92
205
|
void (async () => {
|
|
@@ -99,7 +212,7 @@ export function TourProvider({ children }) {
|
|
|
99
212
|
showProgress: true,
|
|
100
213
|
animate: !reducedMotion,
|
|
101
214
|
allowClose: true,
|
|
102
|
-
steps:
|
|
215
|
+
steps: resolveSteps(role, pathname),
|
|
103
216
|
};
|
|
104
217
|
driver(config).drive();
|
|
105
218
|
})();
|
|
@@ -51,7 +51,7 @@ function applyTheme(theme) {
|
|
|
51
51
|
else
|
|
52
52
|
root.classList.remove("dark");
|
|
53
53
|
// Persist active theme ID so the anti-FOUC script can restore it on return visits,
|
|
54
|
-
// preventing a flash to the built-in
|
|
54
|
+
// preventing a flash to the built-in teal/crimson colors on page load.
|
|
55
55
|
try {
|
|
56
56
|
window.localStorage.setItem(THEME_ID_STORAGE_KEY, theme.id);
|
|
57
57
|
}
|
|
@@ -16,14 +16,14 @@ export const listRootCategories = cache(async () => {
|
|
|
16
16
|
/** Featured categories for homepage display (showOnHomepage: true). */
|
|
17
17
|
export const listFeaturedCategories = cache(async () => {
|
|
18
18
|
return categoriesRepository
|
|
19
|
-
.list({ filters: "isActive==true,
|
|
19
|
+
.list({ filters: "isActive==true,showOnHomepage==true", sorts: "order", page: 1, pageSize: CATEGORIES_FEATURED_LIMIT })
|
|
20
20
|
.then((r) => r.data ?? [])
|
|
21
21
|
.catch(() => []);
|
|
22
22
|
});
|
|
23
23
|
/** Nav-menu categories (showInMenu: true, tier 1 + 2). */
|
|
24
24
|
export const listMenuCategories = cache(async () => {
|
|
25
25
|
return categoriesRepository
|
|
26
|
-
.list({ filters: "isActive==true,display.showInMenu==true", sorts: "
|
|
26
|
+
.list({ filters: "isActive==true,display.showInMenu==true", sorts: "order", page: 1, pageSize: CATEGORIES_MENU_LIMIT })
|
|
27
27
|
.then((r) => r.data ?? [])
|
|
28
28
|
.catch(() => []);
|
|
29
29
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
|
|
3
|
+
// Satori (next/og) needs literal color values, not CSS vars — derived from
|
|
4
|
+
// the theme token record so a rebrand doesn't require re-hardcoding here.
|
|
5
|
+
const OG_FAQ_GRADIENT = `linear-gradient(135deg, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"]} 0%, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary-700"]} 100%)`;
|
|
2
6
|
export function renderFaqOg(category, opts) {
|
|
3
7
|
const label = category
|
|
4
8
|
.replace(/-/g, " ")
|
|
@@ -11,7 +15,7 @@ export function renderFaqOgImage(data) {
|
|
|
11
15
|
display: "flex",
|
|
12
16
|
width: "100%",
|
|
13
17
|
height: "100%",
|
|
14
|
-
background:
|
|
18
|
+
background: OG_FAQ_GRADIENT,
|
|
15
19
|
fontFamily: "sans-serif",
|
|
16
20
|
alignItems: "center",
|
|
17
21
|
justifyContent: "center",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
|
|
3
|
+
// Satori (next/og) needs literal color values, not CSS vars — derived from
|
|
4
|
+
// the theme token record so a rebrand doesn't require re-hardcoding here.
|
|
5
|
+
const OG_AVATAR_GRADIENT = `linear-gradient(135deg, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"]}, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-secondary"]})`;
|
|
2
6
|
function stars(n) {
|
|
3
7
|
return "★".repeat(Math.min(5, Math.max(1, Math.round(n))));
|
|
4
8
|
}
|
|
@@ -41,7 +45,7 @@ export function renderReviewOgImage(data, siteName) {
|
|
|
41
45
|
width: 80,
|
|
42
46
|
height: 80,
|
|
43
47
|
borderRadius: 40,
|
|
44
|
-
background:
|
|
48
|
+
background: OG_AVATAR_GRADIENT,
|
|
45
49
|
display: "flex",
|
|
46
50
|
alignItems: "center",
|
|
47
51
|
justifyContent: "center",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ROUTES } from "../../../../next/routing/route-map";
|
|
2
|
+
import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
|
|
2
3
|
export function buildManifest({ name, shortName, description }) {
|
|
3
4
|
return {
|
|
4
5
|
name,
|
|
@@ -8,7 +9,7 @@ export function buildManifest({ name, shortName, description }) {
|
|
|
8
9
|
display: "standalone",
|
|
9
10
|
orientation: "portrait",
|
|
10
11
|
background_color: "#ffffff",
|
|
11
|
-
theme_color: "
|
|
12
|
+
theme_color: DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"],
|
|
12
13
|
categories: ["shopping", "marketplace", "business"],
|
|
13
14
|
lang: "en",
|
|
14
15
|
dir: "ltr",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ImageResponse } from "next/og";
|
|
3
|
+
import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
|
|
3
4
|
export const DEFAULT_OG_SIZE = { width: 1200, height: 630 };
|
|
5
|
+
// Satori (next/og) cannot resolve CSS custom properties — it needs literal
|
|
6
|
+
// color values baked into the style object. Derived from the theme's own
|
|
7
|
+
// token record (not re-hardcoded) so a future rebrand only requires editing
|
|
8
|
+
// default-light.ts, not hunting down every Satori-rendered image separately.
|
|
9
|
+
const OG_BRAND_GRADIENT = `linear-gradient(135deg, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"]} 0%, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-secondary"]} 50%, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary-800"]} 100%)`;
|
|
4
10
|
/**
|
|
5
11
|
* Resolves a potentially relative image URL to an absolute URL for use in
|
|
6
12
|
* next/og (Satori). Satori cannot resolve relative paths; all <img src> values
|
|
@@ -27,7 +33,7 @@ export function buildDefaultOgImage({ siteName, tagline, domain, logoUrl }) {
|
|
|
27
33
|
flexDirection: "column",
|
|
28
34
|
alignItems: "center",
|
|
29
35
|
justifyContent: "center",
|
|
30
|
-
background:
|
|
36
|
+
background: OG_BRAND_GRADIENT,
|
|
31
37
|
fontFamily: "sans-serif",
|
|
32
38
|
position: "relative",
|
|
33
39
|
overflow: "hidden",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface TestDataFlagged {
|
|
2
|
+
isTestData?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export interface ViewerLike {
|
|
5
|
+
isTester?: boolean;
|
|
6
|
+
role?: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** True when the viewer is allowed to see isTestData:true documents. */
|
|
9
|
+
export declare function canViewTestData(viewer: ViewerLike | null | undefined): boolean;
|
|
10
|
+
/** Strips isTestData:true items from a list unless the viewer is a tester or admin. */
|
|
11
|
+
export declare function filterTestDataForViewer<T extends TestDataFlagged>(items: T[], viewer: ViewerLike | null | undefined): T[];
|
|
12
|
+
/** Returns null (as if not found) for an isTestData:true single doc when the viewer can't see it. */
|
|
13
|
+
export declare function filterSingleTestData<T extends TestDataFlagged>(item: T | null, viewer: ViewerLike | null | undefined): T | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tester sandbox visibility — the shared test store/products/categories/blog/event are
|
|
3
|
+
* created as fully public/active documents so testers exercise the real browse/search/
|
|
4
|
+
* checkout code paths, but must stay invisible to non-testers. Filtering happens at the
|
|
5
|
+
* application layer (post-fetch, in-memory) rather than via a Firestore `where` clause,
|
|
6
|
+
* because `isTestData` is a new optional field — a Firestore inequality filter
|
|
7
|
+
* (`where("isTestData", "!=", true)`) would silently exclude every pre-existing document
|
|
8
|
+
* that doesn't have the field set at all, which is worse than the problem it solves.
|
|
9
|
+
*/
|
|
10
|
+
import { isAdminUser } from "../../../../features/auth/role-predicates";
|
|
11
|
+
/** True when the viewer is allowed to see isTestData:true documents. */
|
|
12
|
+
export function canViewTestData(viewer) {
|
|
13
|
+
return Boolean(viewer?.isTester) || isAdminUser(viewer);
|
|
14
|
+
}
|
|
15
|
+
/** Strips isTestData:true items from a list unless the viewer is a tester or admin. */
|
|
16
|
+
export function filterTestDataForViewer(items, viewer) {
|
|
17
|
+
if (canViewTestData(viewer))
|
|
18
|
+
return items;
|
|
19
|
+
return items.filter((item) => item.isTestData !== true);
|
|
20
|
+
}
|
|
21
|
+
/** Returns null (as if not found) for an isTestData:true single doc when the viewer can't see it. */
|
|
22
|
+
export function filterSingleTestData(item, viewer) {
|
|
23
|
+
if (!item)
|
|
24
|
+
return null;
|
|
25
|
+
if (item.isTestData === true && !canViewTestData(viewer))
|
|
26
|
+
return null;
|
|
27
|
+
return item;
|
|
28
|
+
}
|
|
@@ -14,6 +14,7 @@ export declare const autoPayoutEligibility: import("./types").ScheduledFunctionD
|
|
|
14
14
|
export declare const cleanupRtdbEvents: import("./types").ScheduledFunctionDefinition;
|
|
15
15
|
export declare const mediaTmpCleanup: import("./types").ScheduledFunctionDefinition;
|
|
16
16
|
export declare const draftPrune: import("./types").ScheduledFunctionDefinition;
|
|
17
|
+
export declare const testerSandboxCleanup: import("./types").ScheduledFunctionDefinition;
|
|
17
18
|
export declare const prizeRevealOpen: import("./types").ScheduledFunctionDefinition;
|
|
18
19
|
export declare const prizeRevealClose: import("./types").ScheduledFunctionDefinition;
|
|
19
20
|
export declare const prizeRevealExpiry: import("./types").ScheduledFunctionDefinition;
|
|
@@ -21,4 +22,4 @@ export declare const prizeRevealReminder: import("./types").ScheduledFunctionDef
|
|
|
21
22
|
export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
|
|
22
23
|
export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
|
|
23
24
|
export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
|
|
24
|
-
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];
|
|
25
|
+
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];
|
|
@@ -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, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealOpenHandler, prizeRevealReminderHandler, productStatsSyncHandler, weeklyPayoutEligibilityHandler, } from "../jobs/handlers";
|
|
3
|
+
import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealOpenHandler, prizeRevealReminderHandler, productStatsSyncHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
|
|
4
4
|
import { defineFunction } from "./define";
|
|
5
5
|
const REGION = "asia-south1";
|
|
6
6
|
const EVERY_5_MIN = "every 5 minutes";
|
|
@@ -116,6 +116,13 @@ export const draftPrune = defineFunction({
|
|
|
116
116
|
handler: draftPruneHandler,
|
|
117
117
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
118
118
|
});
|
|
119
|
+
export const testerSandboxCleanup = defineFunction({
|
|
120
|
+
name: "testerSandboxCleanup",
|
|
121
|
+
description: "Delete expired tester QA sandbox test data (daily 05:00 UTC).",
|
|
122
|
+
trigger: { kind: "schedule", cron: "0 5 * * *", timeZone: "UTC" },
|
|
123
|
+
handler: testerSandboxCleanupHandler,
|
|
124
|
+
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
125
|
+
});
|
|
119
126
|
export const prizeRevealOpen = defineFunction({
|
|
120
127
|
name: "prizeRevealOpen",
|
|
121
128
|
description: "Flip prize-draw reveals pending→open and send opening notifications.",
|
|
@@ -182,6 +189,7 @@ export const SCHEDULED_FUNCTIONS = [
|
|
|
182
189
|
cleanupRtdbEvents,
|
|
183
190
|
mediaTmpCleanup,
|
|
184
191
|
draftPrune,
|
|
192
|
+
testerSandboxCleanup,
|
|
185
193
|
prizeRevealOpen,
|
|
186
194
|
prizeRevealClose,
|
|
187
195
|
prizeRevealExpiry,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { runWeeklyPayoutEligibility } from "./weeklyPayoutEligibility";
|
|
12
12
|
import { runHardBanCascade } from "./hardBanCascade";
|
|
13
|
+
import { runResetOtpVerification } from "./resetOtpVerification";
|
|
13
14
|
async function runPayoutsWeeklyJob(_payload, ctx) {
|
|
14
15
|
const { payoutsCreated, ordersProcessed, payoutIds } = await runWeeklyPayoutEligibility(ctx);
|
|
15
16
|
return {
|
|
@@ -31,7 +32,11 @@ async function runHardBanCascadeJob(payload, ctx) {
|
|
|
31
32
|
const bannedBy = String(payload.bannedBy ?? "");
|
|
32
33
|
return runHardBanCascade({ uid, reason, bannedBy }, ctx);
|
|
33
34
|
}
|
|
35
|
+
async function runResetOtpVerificationJob(_payload, ctx) {
|
|
36
|
+
return runResetOtpVerification(ctx);
|
|
37
|
+
}
|
|
34
38
|
export const JOB_RUNNERS = {
|
|
35
39
|
payoutsWeekly: runPayoutsWeeklyJob,
|
|
36
40
|
hardBanCascade: runHardBanCascadeJob,
|
|
41
|
+
resetOtpVerification: runResetOtpVerificationJob,
|
|
37
42
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core: reset OTP/phone verification state — runs when an admin flips
|
|
3
|
+
* `siteSettings.featureFlags.smsVerification` from off to on (see
|
|
4
|
+
* `PATCH /api/site-settings` in the consumer, which enqueues this job on
|
|
5
|
+
* that specific transition).
|
|
6
|
+
*
|
|
7
|
+
* Re-enabling SMS verification after a period of being off means every
|
|
8
|
+
* previously-verified user's `phoneVerified` flag reflects a verification
|
|
9
|
+
* that happened under different rules (or none at all, if verification was
|
|
10
|
+
* off when they signed up) — reset it so everyone re-verifies. The daily
|
|
11
|
+
* global counter and any live per-user cooldowns are also cleared so the
|
|
12
|
+
* freshly-re-enabled feature doesn't inherit rate-limit state from before
|
|
13
|
+
* it was off.
|
|
14
|
+
*/
|
|
15
|
+
import type { JobContext } from "../runtime/types";
|
|
16
|
+
import type { JobRunResult } from "./jobRunners";
|
|
17
|
+
export declare function runResetOtpVerification(ctx: JobContext): Promise<JobRunResult>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core: reset OTP/phone verification state — runs when an admin flips
|
|
3
|
+
* `siteSettings.featureFlags.smsVerification` from off to on (see
|
|
4
|
+
* `PATCH /api/site-settings` in the consumer, which enqueues this job on
|
|
5
|
+
* that specific transition).
|
|
6
|
+
*
|
|
7
|
+
* Re-enabling SMS verification after a period of being off means every
|
|
8
|
+
* previously-verified user's `phoneVerified` flag reflects a verification
|
|
9
|
+
* that happened under different rules (or none at all, if verification was
|
|
10
|
+
* off when they signed up) — reset it so everyone re-verifies. The daily
|
|
11
|
+
* global counter and any live per-user cooldowns are also cleared so the
|
|
12
|
+
* freshly-re-enabled feature doesn't inherit rate-limit state from before
|
|
13
|
+
* it was off.
|
|
14
|
+
*/
|
|
15
|
+
import { USER_COLLECTION } from "../../../../features/auth/schemas/firestore";
|
|
16
|
+
import { SMS_COUNTERS_COLLECTION } from "../../../../features/auth/schemas/firestore";
|
|
17
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
18
|
+
const BATCH_SIZE = 400;
|
|
19
|
+
async function resetPhoneVerifiedFlags(ctx) {
|
|
20
|
+
const failed = [];
|
|
21
|
+
let updated = 0;
|
|
22
|
+
let lastDocId = null;
|
|
23
|
+
for (;;) {
|
|
24
|
+
let query = ctx.db
|
|
25
|
+
.collection(USER_COLLECTION)
|
|
26
|
+
.where("phoneVerified", "==", true)
|
|
27
|
+
.orderBy("__name__")
|
|
28
|
+
.limit(BATCH_SIZE);
|
|
29
|
+
if (lastDocId) {
|
|
30
|
+
query = query.startAfter(lastDocId);
|
|
31
|
+
}
|
|
32
|
+
const snap = await query.get();
|
|
33
|
+
if (snap.empty)
|
|
34
|
+
break;
|
|
35
|
+
const batch = ctx.db.batch();
|
|
36
|
+
for (const doc of snap.docs) {
|
|
37
|
+
batch.update(doc.ref, { phoneVerified: false });
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await batch.commit();
|
|
41
|
+
updated += snap.docs.length;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
void normalizeError(err);
|
|
45
|
+
ctx.logger.error("resetOtpVerification: batch commit failed", err);
|
|
46
|
+
for (const doc of snap.docs)
|
|
47
|
+
failed.push(doc.id);
|
|
48
|
+
}
|
|
49
|
+
lastDocId = snap.docs[snap.docs.length - 1].id;
|
|
50
|
+
if (snap.docs.length < BATCH_SIZE)
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
return { updated, failed };
|
|
54
|
+
}
|
|
55
|
+
async function clearSmsCounters(ctx) {
|
|
56
|
+
let removed = 0;
|
|
57
|
+
for (;;) {
|
|
58
|
+
const snap = await ctx.db.collection(SMS_COUNTERS_COLLECTION).limit(BATCH_SIZE).get();
|
|
59
|
+
if (snap.empty)
|
|
60
|
+
break;
|
|
61
|
+
const batch = ctx.db.batch();
|
|
62
|
+
for (const doc of snap.docs)
|
|
63
|
+
batch.delete(doc.ref);
|
|
64
|
+
await batch.commit();
|
|
65
|
+
removed += snap.docs.length;
|
|
66
|
+
if (snap.docs.length < BATCH_SIZE)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
return removed;
|
|
70
|
+
}
|
|
71
|
+
export async function runResetOtpVerification(ctx) {
|
|
72
|
+
ctx.logger.info("resetOtpVerification: starting (smsVerification re-enabled)");
|
|
73
|
+
const { updated, failed } = await resetPhoneVerifiedFlags(ctx);
|
|
74
|
+
const countersCleared = await clearSmsCounters(ctx);
|
|
75
|
+
ctx.logger.info("resetOtpVerification: done", { updated, failed: failed.length, countersCleared });
|
|
76
|
+
return {
|
|
77
|
+
summary: {
|
|
78
|
+
total: updated + failed.length,
|
|
79
|
+
succeeded: updated,
|
|
80
|
+
skipped: 0,
|
|
81
|
+
failed: failed.length,
|
|
82
|
+
},
|
|
83
|
+
succeeded: [],
|
|
84
|
+
skipped: [],
|
|
85
|
+
failed: failed.map((id) => ({ id, reason: "batch commit failed" })),
|
|
86
|
+
data: { usersReset: updated, smsCountersCleared: countersCleared },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JobContext } from "../runtime/types";
|
|
2
|
+
export interface TesterSandboxCleanupOptions {
|
|
3
|
+
/** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
|
|
4
|
+
force?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function runTesterSandboxCleanup(ctx: JobContext, opts?: TesterSandboxCleanupOptions): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { batchDelete, getTestDataRefs } from "../handlers/_helpers";
|
|
2
|
+
import { BID_FIELDS } from "../../../../constants/field-names";
|
|
3
|
+
const SANDBOX_COLLECTIONS = ["categories", "stores", "products", "blogPosts", "events"];
|
|
4
|
+
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
5
|
+
export async function runTesterSandboxCleanup(ctx, opts = {}) {
|
|
6
|
+
const cutoff = opts.force ? null : new Date();
|
|
7
|
+
ctx.logger.info(opts.force
|
|
8
|
+
? "Force-purging all tester sandbox test data"
|
|
9
|
+
: "Sweeping expired tester sandbox test data");
|
|
10
|
+
const allRefs = [];
|
|
11
|
+
let deletedProductIds = [];
|
|
12
|
+
for (const collection of SANDBOX_COLLECTIONS) {
|
|
13
|
+
const refs = await getTestDataRefs(ctx.db, collection, cutoff);
|
|
14
|
+
allRefs.push(...refs);
|
|
15
|
+
if (collection === "products") {
|
|
16
|
+
deletedProductIds = refs.map((ref) => ref.id);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Cascade into bids referencing deleted test products — bids hold only a live
|
|
20
|
+
// productId FK with no denormalized snapshot, so leaving them behind after the
|
|
21
|
+
// product is gone would create meaningless orphaned auction history.
|
|
22
|
+
for (let i = 0; i < deletedProductIds.length; i += BID_CHUNK_SIZE) {
|
|
23
|
+
const chunk = deletedProductIds.slice(i, i + BID_CHUNK_SIZE);
|
|
24
|
+
if (chunk.length === 0)
|
|
25
|
+
continue;
|
|
26
|
+
const snap = await ctx.db.collection("bids").where(BID_FIELDS.PRODUCT_ID, "in", chunk).get();
|
|
27
|
+
allRefs.push(...snap.docs.map((d) => d.ref));
|
|
28
|
+
}
|
|
29
|
+
if (allRefs.length === 0) {
|
|
30
|
+
ctx.logger.info("No tester sandbox test data to clean up");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const deleted = await batchDelete(ctx, allRefs);
|
|
34
|
+
ctx.logger.info("Tester sandbox cleanup complete", { deleted });
|
|
35
|
+
}
|
|
@@ -6,3 +6,9 @@ import type { JobContext } from "../runtime/types";
|
|
|
6
6
|
/** Batch-delete document refs, respecting Firestore's 500-write batch limit. */
|
|
7
7
|
export declare function batchDelete(ctx: JobContext, refs: FirebaseFirestore.DocumentReference[]): Promise<number>;
|
|
8
8
|
export { getAdminRealtimeDb };
|
|
9
|
+
/**
|
|
10
|
+
* Query docs tagged isTestData:true in a collection. `cutoff: null` returns every
|
|
11
|
+
* tester-sandbox doc regardless of expiry (manual force-purge); a real Date returns
|
|
12
|
+
* only the ones past testDataExpiresAt (the scheduled 7-day sweep).
|
|
13
|
+
*/
|
|
14
|
+
export declare function getTestDataRefs(db: FirebaseFirestore.Firestore, collection: string, cutoff: Date | null): Promise<FirebaseFirestore.DocumentReference[]>;
|