@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
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ListingLayoutProps } from "../../../ui";
|
|
3
|
+
export interface AdminTesterChecklistViewProps extends ListingLayoutProps {
|
|
4
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function AdminTesterChecklistView({ children, onBulkDelete, ...props }: AdminTesterChecklistViewProps): React.JSX.Element;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
|
|
4
|
+
import { sortBy } from "@mohasinac/appkit";
|
|
5
|
+
import React, { useMemo } from "react";
|
|
6
|
+
import { FilterChipGroup, Heading, ListingLayout, Span, Text } from "../../../ui";
|
|
7
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
|
|
9
|
+
import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
|
|
10
|
+
import { DataListingView } from "./DataListingView";
|
|
11
|
+
import { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
|
|
12
|
+
const COLUMNS = [
|
|
13
|
+
{
|
|
14
|
+
key: "primary",
|
|
15
|
+
header: "Test case",
|
|
16
|
+
sortable: true,
|
|
17
|
+
render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { weight: "semibold", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", children: row.secondary })] })),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: "status",
|
|
21
|
+
header: "Status",
|
|
22
|
+
className: "w-32",
|
|
23
|
+
render: (row) => (_jsx(Span, { size: "xs", weight: "medium", className: "inline-flex bg-primary-50 text-primary-800 dark:bg-secondary-900/30 dark:text-secondary-300", rounded: "full", padding: "pill-sm-tall", children: row.status })),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: "updatedAt",
|
|
27
|
+
header: "Updated",
|
|
28
|
+
className: "w-32",
|
|
29
|
+
render: (row) => (_jsx(Text, { size: "sm", color: "muted", children: row.updatedAt })),
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
|
|
33
|
+
const config = useMemo(() => ({
|
|
34
|
+
portal: "admin",
|
|
35
|
+
title: "Tester Checklist",
|
|
36
|
+
searchPlaceholder: "Search test cases, groups, or pages",
|
|
37
|
+
emptyLabel: "No checklist items found",
|
|
38
|
+
filterKeys: ["isActive"],
|
|
39
|
+
defaultSort: sortBy("order", "ASC"),
|
|
40
|
+
queryKey: ["admin", "tester-checklist-items", "listing"],
|
|
41
|
+
endpoint: ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEMS,
|
|
42
|
+
sortOptions: [
|
|
43
|
+
{ value: "order", label: "Order" },
|
|
44
|
+
{ value: sortBy("createdAt", "DESC"), label: "Newest" },
|
|
45
|
+
{ value: "label", label: "Test case A–Z" },
|
|
46
|
+
],
|
|
47
|
+
columns: COLUMNS,
|
|
48
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
49
|
+
id: toStringValue(item.id, `checklist-${index}`),
|
|
50
|
+
primary: toStringValue(item.label, "Untitled test case"),
|
|
51
|
+
secondary: `${toStringValue(item.groupLabel, "Uncategorized")} / ${toStringValue(item.pageLabel, "")}`,
|
|
52
|
+
status: toStringValue(typeof item.isActive === "boolean"
|
|
53
|
+
? item.isActive
|
|
54
|
+
? "Active"
|
|
55
|
+
: "Inactive"
|
|
56
|
+
: item.status, "Active"),
|
|
57
|
+
updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
|
|
58
|
+
})),
|
|
59
|
+
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
60
|
+
buildFilters: (state) => state.isActive ? sieveFilter("isActive", SIEVE_OP.EQ, state.isActive) : undefined,
|
|
61
|
+
primaryAction: {
|
|
62
|
+
label: "Add Test Case",
|
|
63
|
+
onClick: ({ openCreatePanel }) => openCreatePanel(),
|
|
64
|
+
},
|
|
65
|
+
// Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
|
|
66
|
+
buildBulkActions: (selection) => {
|
|
67
|
+
const handlers = {
|
|
68
|
+
[ROW_ACTION_ID.DELETE]: onBulkDelete
|
|
69
|
+
? async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); }
|
|
70
|
+
: undefined,
|
|
71
|
+
};
|
|
72
|
+
return ADMIN_BULK_ACTIONS.testerChecklistItems
|
|
73
|
+
.filter((id) => handlers[id])
|
|
74
|
+
.map((id) => ({
|
|
75
|
+
id,
|
|
76
|
+
label: ROW_ACTION_META[id].label,
|
|
77
|
+
destructive: ROW_ACTION_META[id].destructive,
|
|
78
|
+
onClick: handlers[id],
|
|
79
|
+
}));
|
|
80
|
+
},
|
|
81
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: [
|
|
82
|
+
{ id: "All", label: "All" },
|
|
83
|
+
{ id: "true", label: "Active" },
|
|
84
|
+
{ id: "false", label: "Inactive" },
|
|
85
|
+
], value: pendingFilters.isActive || "", onChange: (v) => setPendingFilters((p) => ({ ...p, isActive: v })) })),
|
|
86
|
+
renderEditor: ({ editId, closePanel }) => (_jsx(AdminTesterChecklistItemEditorView, { itemId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })),
|
|
87
|
+
resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Test Case" : "Edit Test Case"),
|
|
88
|
+
}), [onBulkDelete]);
|
|
89
|
+
if (React.Children.count(children) > 0) {
|
|
90
|
+
return (_jsxs(ListingLayout, { portal: "admin", ...props, children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Tester Checklist" }), children] }));
|
|
91
|
+
}
|
|
92
|
+
return (_jsxs(_Fragment, { children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Tester Checklist" }), _jsx(DataListingView, { config: config })] }));
|
|
93
|
+
}
|
|
@@ -14,6 +14,8 @@ export interface AdminUserEditorViewProps {
|
|
|
14
14
|
currentRole?: string;
|
|
15
15
|
currentIsDisabled?: boolean;
|
|
16
16
|
currentEmailVerified?: boolean;
|
|
17
|
+
/** Tester program flag — orthogonal to role. Grants access to the Tester Hub and auto-approves the user's store. */
|
|
18
|
+
currentIsTester?: boolean;
|
|
17
19
|
/** Store the user owns (for sellers/admins). storeId === storeSlug in this project. */
|
|
18
20
|
ownedStoreId?: string;
|
|
19
21
|
ownedStoreName?: string;
|
|
@@ -33,5 +35,5 @@ export interface AdminUserEditorViewProps {
|
|
|
33
35
|
linkedin?: string;
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
|
-
export declare function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }: AdminUserEditorViewProps): React.JSX.Element;
|
|
38
|
+
export declare function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, currentIsTester, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }: AdminUserEditorViewProps): React.JSX.Element;
|
|
37
39
|
export {};
|
|
@@ -41,12 +41,13 @@ function SoftBanPanel({ userId, softBans, showAddSoftBan, setShowAddSoftBan, sof
|
|
|
41
41
|
return (_jsxs(Div, { surface: "muted", padding: "sm", rounded: "lg", border: "default", children: [_jsxs(Row, { justify: "between", gap: "sm", className: "mb-2", children: [_jsxs(Span, { size: "sm", weight: "medium", color: "muted", children: ["Soft bans", softBans.length > 0 ? ` (${softBans.length})` : ""] }), !showAddSoftBan && (_jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: !userId, onClick: () => setShowAddSoftBan(true), children: "Add soft ban" }))] }), softBans.length > 0 && (_jsx(Stack, { as: "ul", gap: "xs", className: "mb-3", children: softBans.map((ban) => (_jsxs(Row, { as: "li", align: "start", justify: "between", gap: "xs", surface: "default", padding: "inlineSm", rounded: "md", border: "default", children: [_jsxs(Stack, { gap: "none", className: "min-w-0 flex-1", children: [_jsx(Span, { size: "xs", weight: "semibold", children: formatBanAction(ban.action) }), _jsx(Span, { size: "xs", color: "muted", children: ban.reason }), _jsx(Span, { size: "xs", color: "muted", children: formatExpiry(ban.expiresAt) })] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: liftPending, disabled: liftPending, onClick: () => onLiftSoftBan(ban.action), children: "Lift" })] }, ban.action))) })), showAddSoftBan && (_jsxs(Stack, { gap: "xs", children: [_jsx(Select, { label: "Action to restrict", options: BANNED_ACTION_OPTIONS, value: softBanAction, onValueChange: setSoftBanAction }), _jsx(Textarea, { label: "Reason (required)", value: softBanReason, onChange: (e) => setSoftBanReason(e.target.value), rows: 2, placeholder: "e.g. Suspicious bid activity\u2026" }), _jsx(Input, { label: "Expires at (optional \u2014 leave blank for permanent)", type: "datetime-local", value: softBanExpiry, onChange: (e) => setSoftBanExpiry(e.target.value) }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: softBanPending, disabled: !softBanReason.trim() || softBanPending, onClick: () => onAddSoftBan({ action: softBanAction, reason: softBanReason.trim(), ...(softBanExpiry ? { expiresAt: new Date(softBanExpiry).toISOString() } : {}) }), children: "Apply soft ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => { setShowAddSoftBan(false); setSoftBanReason(""); setSoftBanExpiry(""); }, children: "Cancel" })] })] })), softBans.length === 0 && !showAddSoftBan && (_jsx(Text, { size: "xs", color: "muted", children: "No active soft bans." }))] }));
|
|
42
42
|
}
|
|
43
43
|
// --- Component ---------------------------------------------------------------
|
|
44
|
-
export function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }) {
|
|
44
|
+
export function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, currentIsTester, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }) {
|
|
45
45
|
const queryClient = useQueryClient();
|
|
46
46
|
const { showToast } = useToast();
|
|
47
47
|
// --- General fields -------------------------------------------------------
|
|
48
48
|
const [role, setRole] = React.useState(currentRole ?? "user");
|
|
49
49
|
const [emailVerified, setEmailVerified] = React.useState(currentEmailVerified ?? false);
|
|
50
|
+
const [isTester, setIsTester] = React.useState(currentIsTester ?? false);
|
|
50
51
|
const [adminNotes, setAdminNotes] = React.useState("");
|
|
51
52
|
const [deleteOpen, setDeleteOpen] = React.useState(false);
|
|
52
53
|
// --- ST-2 profile fields --------------------------------------------------
|
|
@@ -71,6 +72,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
|
|
|
71
72
|
if (open) {
|
|
72
73
|
setRole(currentRole ?? "user");
|
|
73
74
|
setEmailVerified(currentEmailVerified ?? false);
|
|
75
|
+
setIsTester(currentIsTester ?? false);
|
|
74
76
|
setAdminNotes("");
|
|
75
77
|
setEditDisplayName(displayName ?? "");
|
|
76
78
|
setPhoneNumber(currentPhoneNumber ?? "");
|
|
@@ -92,6 +94,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
|
|
|
92
94
|
open,
|
|
93
95
|
currentRole,
|
|
94
96
|
currentEmailVerified,
|
|
97
|
+
currentIsTester,
|
|
95
98
|
displayName,
|
|
96
99
|
currentPhoneNumber,
|
|
97
100
|
currentBio,
|
|
@@ -129,6 +132,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
|
|
|
129
132
|
await apiClient.patch(ADMIN_ENDPOINTS.USER_BY_ID(userId), {
|
|
130
133
|
role,
|
|
131
134
|
emailVerified,
|
|
135
|
+
isTester,
|
|
132
136
|
adminNotes: adminNotes || undefined,
|
|
133
137
|
displayName: editDisplayName.trim() || undefined,
|
|
134
138
|
phoneNumber: phoneNumber.trim() || undefined,
|
|
@@ -219,6 +223,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
|
|
|
219
223
|
const renderInfoCard = () => userId ? (_jsx(Div, { textSize: "xs", surface: "muted", rounded: "lg", border: "default", padding: "inlineSm", children: _jsxs(Stack, { color: "primary", gap: "xs", children: [_jsxs(Text, { size: "xs", children: [_jsx(Span, { weight: "semibold", children: "Owner ID (Firebase UID):" }), " ", _jsx(Code, { className: "select-all font-mono", children: userId })] }), ownedStoreId && (_jsxs(Text, { size: "xs", children: [_jsx(Span, { weight: "semibold", children: "Owns store:" }), " ", _jsx(Code, { className: "select-all font-mono", children: ownedStoreId }), ownedStoreName ? ` — ${ownedStoreName}` : ""] }))] }) })) : null;
|
|
220
224
|
const renderRoleSection = () => (_jsx(Select, { label: "Role", options: ROLE_OPTIONS, value: role, onValueChange: setRole }));
|
|
221
225
|
const renderEmailVerifiedSection = () => (_jsx(Toggle, { label: "Email verified", checked: emailVerified, onChange: setEmailVerified }));
|
|
226
|
+
const renderIsTesterSection = () => (_jsx(Toggle, { label: "Is Tester", checked: isTester, onChange: setIsTester }));
|
|
222
227
|
const renderProfileSection = () => (_jsxs(Stack, { border: "default", as: "section", gap: "sm", className: "border-t", padding: "t-sm", children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Profile details" }), _jsx(Input, { label: "Display name", value: editDisplayName, onChange: (e) => setEditDisplayName(e.target.value), placeholder: "Full name shown on profile" }), _jsx(Input, { label: "Phone number", type: "tel", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "+91 90000 00000" }), _jsx(Textarea, { label: "Bio", value: bio, onChange: (e) => setBio(e.target.value), rows: 3, placeholder: "Short bio shown on the public profile\u2026" }), _jsx(Input, { label: "Location", value: location, onChange: (e) => setLocation(e.target.value), placeholder: "City, Country" }), _jsx(Input, { label: "Website", type: "url", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://" }), _jsx(Text, { size: "xs", color: "muted", children: "Social links" }), _jsx(Input, { label: "Twitter / X", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "@handle or full URL" }), _jsx(Input, { label: "Instagram", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "@handle or full URL" }), _jsx(Input, { label: "Facebook", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/\u2026" }), _jsx(Input, { label: "LinkedIn", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/in/\u2026" })] }));
|
|
223
228
|
const renderAdminNotesSection = () => (_jsx(Textarea, { label: "Admin notes (optional)", value: adminNotes, onChange: (e) => setAdminNotes(e.target.value), rows: 3, placeholder: "Internal notes about this user\u2026" }));
|
|
224
229
|
const renderActionsSection = () => (_jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "danger", onClick: () => setDeleteOpen(true), disabled: !userId, children: "Delete user" }), _jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !userId || saveMutation.isPending, children: "Save changes" })] }));
|
|
@@ -230,6 +235,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
|
|
|
230
235
|
renderInfoCard,
|
|
231
236
|
renderRoleSection,
|
|
232
237
|
renderEmailVerifiedSection,
|
|
238
|
+
renderIsTesterSection,
|
|
233
239
|
renderProfileSection,
|
|
234
240
|
renderAdminNotesSection,
|
|
235
241
|
renderActionsSection,
|
|
@@ -214,7 +214,7 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
214
214
|
},
|
|
215
215
|
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_USER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Role", tabs: ADMIN_USER_ROLE_TABS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: id })) })] })),
|
|
216
216
|
};
|
|
217
|
-
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
|
|
217
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), currentIsTester: Boolean(selectedRow?._raw?.isTester), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
|
|
218
218
|
selectedRow?._raw?.hardBanReason), currentHardBanReason: toStringValue(selectedRow?._raw?.hardBanReason, "") || undefined, currentSoftBans: Array.isArray(selectedRow?._raw?.softBans)
|
|
219
219
|
? selectedRow._raw.softBans.map((b) => ({
|
|
220
220
|
action: toStringValue(b.action, ""),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Plus } from "lucide-react";
|
|
4
|
-
import { BulkActionBar, Button, Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, SideDrawer } from "../../../ui";
|
|
4
|
+
import { BulkActionBar, Button, Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, SideDrawer, StickyToolbar } from "../../../ui";
|
|
5
5
|
import { useBottomActions } from "../../layout";
|
|
6
6
|
import { useAdminListing } from "../hooks/useAdminListing";
|
|
7
7
|
import { AdminViewCards } from "./AdminViewCards";
|
|
@@ -44,7 +44,7 @@ export function DataListingView({ config, }) {
|
|
|
44
44
|
const isEditorOpen = panel.isCreateOpen || panel.isEditOpen;
|
|
45
45
|
return (_jsxs(Div, { className: config.className ?? "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: config.searchPlaceholder, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || config.defaultSort, sortOptions: config.sortOptions, onSortChange: (v) => table.set("sort", v), showTableView: !config.hideTableView, toggles: config.toggles, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: config.primaryAction || config.toolbarExtra ? (_jsxs(Row, { align: "center", gap: "sm", children: [config.toolbarExtra, config.primaryAction && (_jsxs(Button, { gap: "sm", size: "sm", onClick: () => config.primaryAction.onClick({
|
|
46
46
|
openCreatePanel: panel.openCreatePanel,
|
|
47
|
-
}), children: [config.primaryAction.icon ?? _jsx(Plus, { className: "h-4 w-4" }), config.primaryAction.label] }))] })) : undefined }), config.renderAboveContent?.(), bulkActionItems && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActionItems })), totalPages > 1 && (_jsx(
|
|
47
|
+
}), children: [config.primaryAction.icon ?? _jsx(Plus, { className: "h-4 w-4" }), config.primaryAction.label] }))] })) : undefined }), config.renderAboveContent?.(), bulkActionItems && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActionItems })), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p), pageSize: pageSize, onPageSizeChange: setPageSize, paginationConfig: { showPageSizeSelector: true, pageSizeOptions: [10, 25, 50, 100] } }) }) })), _jsxs(Div, { paddingX: "x-sm-md", padding: "y-md", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), view === "table" ? (_jsx(DataTable, { columns: config.columns, rows: rows, isLoading: isLoading, emptyLabel: config.emptyLabel ?? `No ${config.title.toLowerCase()} found`, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
|
|
48
48
|
? selection.setSelectedIds(rows.map((r) => r.id))
|
|
49
49
|
: selection.clearSelection(), getRowHref: config.getRowHref, onRowClick: config.onRowClick
|
|
50
50
|
? (row) => config.onRowClick(row, {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AdminPageViewsReportViewProps {
|
|
2
|
+
startDate: string;
|
|
3
|
+
endDate: string;
|
|
4
|
+
}
|
|
5
|
+
/** Admin analytics tab — page views for the requested date range, grouped by
|
|
6
|
+
* entity (product/store/category/homepage/auction/review/user-profile) and
|
|
7
|
+
* by raw URL. Backed by the day-bucketed pageViews collection. */
|
|
8
|
+
export declare function AdminPageViewsReportView({ startDate, endDate }: AdminPageViewsReportViewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { Alert, Div, Stack, Text } from "../../../../ui";
|
|
5
|
+
import { DataTable } from "../DataTable";
|
|
6
|
+
import { apiClient } from "../../../../http";
|
|
7
|
+
import { ADMIN_ENDPOINTS } from "../../../../constants/api-endpoints";
|
|
8
|
+
/** Admin analytics tab — page views for the requested date range, grouped by
|
|
9
|
+
* entity (product/store/category/homepage/auction/review/user-profile) and
|
|
10
|
+
* by raw URL. Backed by the day-bucketed pageViews collection. */
|
|
11
|
+
export function AdminPageViewsReportView({ startDate, endDate }) {
|
|
12
|
+
const endpoint = `${ADMIN_ENDPOINTS.ANALYTICS_PAGE_VIEWS}?startDate=${startDate}&endDate=${endDate}`;
|
|
13
|
+
const query = useQuery({
|
|
14
|
+
queryKey: ["admin-page-views", endpoint],
|
|
15
|
+
queryFn: () => apiClient.get(endpoint),
|
|
16
|
+
staleTime: 60000,
|
|
17
|
+
});
|
|
18
|
+
if (query.error) {
|
|
19
|
+
return (_jsx(Alert, { variant: "error", title: "Could not load page views", children: query.error instanceof Error ? query.error.message : "Unknown error" }));
|
|
20
|
+
}
|
|
21
|
+
const data = query.data;
|
|
22
|
+
return (_jsxs(Stack, { gap: "lg", children: [_jsx(Text, { size: "sm", color: "muted", children: data ? `${data.total.toLocaleString()} total views, ${startDate} – ${endDate}` : "Loading…" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", className: "mb-2", children: "By entity" }), _jsx(DataTable, { columns: [
|
|
23
|
+
{ key: "entityType", header: "Type" },
|
|
24
|
+
{ key: "entityId", header: "Entity" },
|
|
25
|
+
{ key: "url", header: "URL" },
|
|
26
|
+
{ key: "count", header: "Views" },
|
|
27
|
+
], rows: (data?.byEntity ?? []).map((row) => ({
|
|
28
|
+
id: `${row.entityType}:${row.entityId}`,
|
|
29
|
+
entityType: row.entityType,
|
|
30
|
+
entityId: row.entityId,
|
|
31
|
+
url: row.url,
|
|
32
|
+
count: row.count.toLocaleString(),
|
|
33
|
+
})), isLoading: query.isLoading, emptyLabel: "No page views recorded for this range." })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", className: "mb-2", children: "By URL" }), _jsx(DataTable, { columns: [
|
|
34
|
+
{ key: "url", header: "URL" },
|
|
35
|
+
{ key: "count", header: "Views" },
|
|
36
|
+
], rows: (data?.byUrl ?? []).map((row) => ({
|
|
37
|
+
id: row.url,
|
|
38
|
+
url: row.url,
|
|
39
|
+
count: row.count.toLocaleString(),
|
|
40
|
+
})), isLoading: query.isLoading, emptyLabel: "No page views recorded for this range." })] })] }));
|
|
41
|
+
}
|
|
@@ -65,6 +65,10 @@ export { AdminFaqsView } from "./AdminFaqsView";
|
|
|
65
65
|
export type { AdminFaqsViewProps } from "./AdminFaqsView";
|
|
66
66
|
export { AdminFaqEditorView } from "./AdminFaqEditorView";
|
|
67
67
|
export type { AdminFaqEditorViewProps } from "./AdminFaqEditorView";
|
|
68
|
+
export { AdminTesterChecklistView } from "./AdminTesterChecklistView";
|
|
69
|
+
export type { AdminTesterChecklistViewProps } from "./AdminTesterChecklistView";
|
|
70
|
+
export { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
|
|
71
|
+
export type { AdminTesterChecklistItemEditorViewProps } from "./AdminTesterChecklistItemEditorView";
|
|
68
72
|
export { AdminNavEditorView } from "./AdminNavEditorView";
|
|
69
73
|
export type { AdminNavEditorViewProps, NavItemData } from "./AdminNavEditorView";
|
|
70
74
|
export { AdminMediaView } from "./AdminMediaView";
|
|
@@ -127,6 +131,8 @@ export { AdminRevenueChart, AdminOrdersChart, } from "./analytics/AdminAnalytics
|
|
|
127
131
|
export type { AdminRevenueChartProps, AdminOrdersChartProps, } from "./analytics/AdminAnalyticsCharts";
|
|
128
132
|
export { AdminTopProductsTable } from "./analytics/AdminTopProductsTable";
|
|
129
133
|
export type { AdminTopProductsTableProps, AdminTopProductsTableLabels, } from "./analytics/AdminTopProductsTable";
|
|
134
|
+
export { AdminPageViewsReportView } from "./analytics/AdminPageViewsReportView";
|
|
135
|
+
export type { AdminPageViewsReportViewProps } from "./analytics/AdminPageViewsReportView";
|
|
130
136
|
export { AdminFeatureFlagsView } from "./AdminFeatureFlagsView";
|
|
131
137
|
export type { AdminFeatureFlagsViewProps } from "./AdminFeatureFlagsView";
|
|
132
138
|
export { AdminNewsletterView } from "./AdminNewsletterView";
|
|
@@ -37,6 +37,8 @@ export { AdminBlogView } from "./AdminBlogView";
|
|
|
37
37
|
export { AdminBlogEditorView } from "./AdminBlogEditorView";
|
|
38
38
|
export { AdminFaqsView } from "./AdminFaqsView";
|
|
39
39
|
export { AdminFaqEditorView } from "./AdminFaqEditorView";
|
|
40
|
+
export { AdminTesterChecklistView } from "./AdminTesterChecklistView";
|
|
41
|
+
export { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
|
|
40
42
|
export { AdminNavEditorView } from "./AdminNavEditorView";
|
|
41
43
|
export { AdminMediaView } from "./AdminMediaView";
|
|
42
44
|
export { AdminStoresView } from "./AdminStoresView";
|
|
@@ -68,6 +70,7 @@ export { AdminPageHeader } from "./AdminPageHeader";
|
|
|
68
70
|
export { DrawerFormFooter } from "./DrawerFormFooter";
|
|
69
71
|
export { AdminRevenueChart, AdminOrdersChart, } from "./analytics/AdminAnalyticsCharts";
|
|
70
72
|
export { AdminTopProductsTable } from "./analytics/AdminTopProductsTable";
|
|
73
|
+
export { AdminPageViewsReportView } from "./analytics/AdminPageViewsReportView";
|
|
71
74
|
export { AdminFeatureFlagsView } from "./AdminFeatureFlagsView";
|
|
72
75
|
export { AdminNewsletterView } from "./AdminNewsletterView";
|
|
73
76
|
export { AdminContactView } from "./AdminContactView";
|
|
@@ -382,15 +382,8 @@ export interface SiteSettingsDocument extends BaseDocument {
|
|
|
382
382
|
notifications: boolean;
|
|
383
383
|
sellerRegistration: boolean;
|
|
384
384
|
preOrders: boolean;
|
|
385
|
-
seedPanel: boolean;
|
|
386
385
|
/** When true, admin users see a bypass button in checkout that skips OTP and payment. Server-enforced. */
|
|
387
386
|
adminCheckoutBypass?: boolean;
|
|
388
|
-
/**
|
|
389
|
-
* Track H — when true and NODE_ENV !== "production", the payment provider
|
|
390
|
-
* resolver returns MockRazorpayProvider. In production the resolver throws
|
|
391
|
-
* if this is ever true. Default false.
|
|
392
|
-
*/
|
|
393
|
-
useMockPayment?: boolean;
|
|
394
387
|
listingTypes?: {
|
|
395
388
|
standard?: boolean;
|
|
396
389
|
auction?: boolean;
|
|
@@ -211,8 +211,12 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
|
|
|
211
211
|
},
|
|
212
212
|
featureFlags: {
|
|
213
213
|
chats: true,
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
// Real per-SMS cost via the Firebase phone-auth quota — kept off by
|
|
215
|
+
// default even though every other flag defaults on. Admin opts in
|
|
216
|
+
// explicitly; PATCH /api/site-settings resets all users' phoneVerified +
|
|
217
|
+
// rate-limit state on the false->true transition (resetOtpVerification job).
|
|
218
|
+
smsVerification: false,
|
|
219
|
+
translations: true,
|
|
216
220
|
wishlists: true,
|
|
217
221
|
auctions: true,
|
|
218
222
|
reviews: true,
|
|
@@ -222,10 +226,6 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
|
|
|
222
226
|
notifications: true,
|
|
223
227
|
sellerRegistration: true,
|
|
224
228
|
preOrders: true,
|
|
225
|
-
seedPanel: true,
|
|
226
|
-
// Track H — both mock-provider flags default to false. Production
|
|
227
|
-
// deployments must never flip these on (the resolver throws if they are).
|
|
228
|
-
useMockPayment: false,
|
|
229
229
|
// W1-37 2026-05-23 — Phase 2 listing types enabled by default; all per-type
|
|
230
230
|
// surfaces (seller + admin via W1-29 + public) are now shipped.
|
|
231
231
|
listingTypes: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PageViewEntityType } from "../types";
|
|
2
|
+
export interface PageViewTrackerProps {
|
|
3
|
+
entityType: PageViewEntityType;
|
|
4
|
+
entityId: string;
|
|
5
|
+
/** Raw pathname to record alongside the entity — pass `usePathname()`'s value. */
|
|
6
|
+
url: string;
|
|
7
|
+
}
|
|
8
|
+
/** Drop into any public detail page (client or server component tree) to
|
|
9
|
+
* record one view on mount. Fire-and-forget — a failed ping never surfaces
|
|
10
|
+
* to the visitor. Fires once per entityId/url pair per mount, not on every
|
|
11
|
+
* re-render. */
|
|
12
|
+
export declare function PageViewTracker({ entityType, entityId, url }: PageViewTrackerProps): null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { apiClient } from "../../../http";
|
|
4
|
+
import { ANALYTICS_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
5
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
6
|
+
/** Drop into any public detail page (client or server component tree) to
|
|
7
|
+
* record one view on mount. Fire-and-forget — a failed ping never surfaces
|
|
8
|
+
* to the visitor. Fires once per entityId/url pair per mount, not on every
|
|
9
|
+
* re-render. */
|
|
10
|
+
export function PageViewTracker({ entityType, entityId, url }) {
|
|
11
|
+
const firedRef = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const key = `${entityType}:${entityId}:${url}`;
|
|
14
|
+
if (firedRef.current === key)
|
|
15
|
+
return;
|
|
16
|
+
firedRef.current = key;
|
|
17
|
+
apiClient.post(ANALYTICS_ENDPOINTS.PAGE_VIEW, { entityType, entityId, url }).catch((err) => {
|
|
18
|
+
void normalizeError(err); // instrumentation-only — never surfaced to the visitor
|
|
19
|
+
});
|
|
20
|
+
}, [entityType, entityId, url]);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { PAGE_VIEW_ENTITY_TYPES, type PageViewEntityType } from "../types";
|
|
3
|
+
export { PAGE_VIEW_ENTITY_TYPES };
|
|
4
|
+
export type { PageViewEntityType };
|
|
5
|
+
export interface PageViewDocument {
|
|
6
|
+
/** `{date}_{entityType}_{entityId}` — one doc per entity per day. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** YYYY-MM-DD, server date at record time. */
|
|
9
|
+
date: string;
|
|
10
|
+
entityType: PageViewEntityType;
|
|
11
|
+
entityId: string;
|
|
12
|
+
/** Raw pathname (e.g. "/products/product-charizard-psa9") — always recorded
|
|
13
|
+
* alongside the entity breakdown so the admin report can also group by URL. */
|
|
14
|
+
url: string;
|
|
15
|
+
count: number;
|
|
16
|
+
updatedAt: Date;
|
|
17
|
+
}
|
|
18
|
+
export interface RecordPageViewInput {
|
|
19
|
+
entityType: PageViewEntityType;
|
|
20
|
+
entityId: string;
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class PageViewsRepository extends BaseRepository<PageViewDocument> {
|
|
24
|
+
constructor();
|
|
25
|
+
/** Fire-and-forget increment — one write per view, day-bucketed so the
|
|
26
|
+
* admin report can filter by date range without storing per-event rows. */
|
|
27
|
+
recordView({ entityType, entityId, url }: RecordPageViewInput): Promise<void>;
|
|
28
|
+
/** All day-buckets in [startDate, endDate] (inclusive, YYYY-MM-DD), optionally
|
|
29
|
+
* scoped to one entityType. Used by the admin page-views report tab. */
|
|
30
|
+
listInRange(startDate: string, endDate: string, entityType?: PageViewEntityType): Promise<PageViewDocument[]>;
|
|
31
|
+
}
|
|
32
|
+
export declare const pageViewsRepository: PageViewsRepository;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
2
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
3
|
+
import { PAGE_VIEW_ENTITY_TYPES } from "../types";
|
|
4
|
+
import { PAGE_VIEW_FIELDS } from "../../../constants/field-names";
|
|
5
|
+
export { PAGE_VIEW_ENTITY_TYPES };
|
|
6
|
+
function todayDateKey() {
|
|
7
|
+
return new Date().toISOString().slice(0, 10);
|
|
8
|
+
}
|
|
9
|
+
function docId(date, entityType, entityId) {
|
|
10
|
+
return `${date}_${entityType}_${entityId}`;
|
|
11
|
+
}
|
|
12
|
+
export class PageViewsRepository extends BaseRepository {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("pageViews");
|
|
15
|
+
}
|
|
16
|
+
/** Fire-and-forget increment — one write per view, day-bucketed so the
|
|
17
|
+
* admin report can filter by date range without storing per-event rows. */
|
|
18
|
+
async recordView({ entityType, entityId, url }) {
|
|
19
|
+
const date = todayDateKey();
|
|
20
|
+
const id = docId(date, entityType, entityId);
|
|
21
|
+
await this.getCollection().doc(id).set({
|
|
22
|
+
date,
|
|
23
|
+
entityType,
|
|
24
|
+
entityId,
|
|
25
|
+
url,
|
|
26
|
+
count: FieldValue.increment(1),
|
|
27
|
+
updatedAt: new Date(),
|
|
28
|
+
}, { merge: true });
|
|
29
|
+
}
|
|
30
|
+
/** All day-buckets in [startDate, endDate] (inclusive, YYYY-MM-DD), optionally
|
|
31
|
+
* scoped to one entityType. Used by the admin page-views report tab. */
|
|
32
|
+
async listInRange(startDate, endDate, entityType) {
|
|
33
|
+
let query = this.getCollection()
|
|
34
|
+
.where(PAGE_VIEW_FIELDS.DATE, ">=", startDate)
|
|
35
|
+
.where(PAGE_VIEW_FIELDS.DATE, "<=", endDate);
|
|
36
|
+
if (entityType) {
|
|
37
|
+
query = query.where(PAGE_VIEW_FIELDS.ENTITY_TYPE, "==", entityType);
|
|
38
|
+
}
|
|
39
|
+
const snap = await query.get();
|
|
40
|
+
return snap.docs.map((d) => this.mapDoc(d));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const pageViewsRepository = new PageViewsRepository();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Shared client+server type for page-view tracking — kept out of
|
|
2
|
+
* page-views.repository.ts (server-only, imports firebase-admin) so the
|
|
3
|
+
* client-side tracker component can import just this without pulling
|
|
4
|
+
* firebase-admin into the client bundle. */
|
|
5
|
+
export declare const PAGE_VIEW_ENTITY_TYPES: readonly ["product", "store", "category", "homepage", "auction", "review", "user-profile"];
|
|
6
|
+
export type PageViewEntityType = (typeof PAGE_VIEW_ENTITY_TYPES)[number];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Shared client+server type for page-view tracking — kept out of
|
|
2
|
+
* page-views.repository.ts (server-only, imports firebase-admin) so the
|
|
3
|
+
* client-side tracker component can import just this without pulling
|
|
4
|
+
* firebase-admin into the client bundle. */
|
|
5
|
+
export const PAGE_VIEW_ENTITY_TYPES = [
|
|
6
|
+
"product",
|
|
7
|
+
"store",
|
|
8
|
+
"category",
|
|
9
|
+
"homepage",
|
|
10
|
+
"auction",
|
|
11
|
+
"review",
|
|
12
|
+
"user-profile",
|
|
13
|
+
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Div, Row, Span } from "../../../ui";
|
|
3
3
|
export function SocialAuthButtons({ onGoogle, disabled, dividerLabel, googleLabel, }) {
|
|
4
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "relative", children: [_jsx(Row, { className: "absolute inset-0", align: "center", children: _jsx(Div, { className: "w-full border-t border-neutral-200" }) }), _jsx(Row, { textSize: "sm", className: "relative", justify: "center", children: _jsx(Span, {
|
|
4
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "relative", children: [_jsx(Row, { className: "absolute inset-0", align: "center", children: _jsx(Div, { className: "w-full border-t border-neutral-200" }) }), _jsx(Row, { textSize: "sm", className: "relative", justify: "center", children: _jsx(Span, { padding: "x-xs", surface: "default", color: "muted", children: dividerLabel }) })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1", children: _jsxs(Button, { rounded: "md", type: "button", onClick: onGoogle, disabled: disabled, variant: "outline", paddingX: "md", paddingY: "sm", textSize: "sm", weight: "medium", className: "inline- justify-[center] border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)] dark:text-[var(--appkit-color-text)] hover:bg-neutral-50 hover:bg-[var(--appkit-color-surface-elevated)] disabled:cursor-not-allowed disabled:opacity-50", children: [_jsxs("svg", { className: "mr-2 h-5 w-5", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { fill: "currentColor", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "currentColor", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "currentColor", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "currentColor", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] }), googleLabel] }) })] }));
|
|
5
5
|
}
|
|
@@ -58,6 +58,18 @@ export declare function useGoogleLogin(options?: {
|
|
|
58
58
|
mutate: () => Promise<void>;
|
|
59
59
|
isLoading: boolean;
|
|
60
60
|
};
|
|
61
|
+
/** Connects the current (already-logged-in) user's account to a Google account,
|
|
62
|
+
* without switching sessions. Reuses the same popup + RTDB/postMessage signal
|
|
63
|
+
* mechanism as useGoogleLogin, but POSTs { mode: "link" } to EVENT_INIT so the
|
|
64
|
+
* server callback records the link on the caller's own profile instead of
|
|
65
|
+
* resolving/creating a separate Firebase Auth user. */
|
|
66
|
+
export declare function useLinkGoogleAccount(options?: {
|
|
67
|
+
onSuccess?: () => void;
|
|
68
|
+
onError?: (error: Error) => void;
|
|
69
|
+
}): {
|
|
70
|
+
mutate: () => Promise<void>;
|
|
71
|
+
isLoading: boolean;
|
|
72
|
+
};
|
|
61
73
|
export declare function useRegister(options?: {
|
|
62
74
|
onSuccess?: (data: JsonValue) => void;
|
|
63
75
|
onError?: (error: Error) => void;
|