@mohasinac/appkit 3.2.0 → 3.2.2

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.
Files changed (31) hide show
  1. package/dist/_internal/server/features/categories/data.js +4 -1
  2. package/dist/_internal/server/features/products/data.js +5 -1
  3. package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +1 -0
  4. package/dist/_internal/server/jobs/core/adminAnalytics.js +1 -0
  5. package/dist/constants/field-names.d.ts +1 -0
  6. package/dist/constants/field-names.js +1 -0
  7. package/dist/features/admin/components/analytics/AdminTopProductsTable.d.ts +1 -0
  8. package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
  9. package/dist/features/admin/schemas/firestore.d.ts +1 -0
  10. package/dist/features/admin/schemas/firestore.js +1 -0
  11. package/dist/features/admin/types/index.d.ts +1 -0
  12. package/dist/features/categories/repository/categories.repository.d.ts +1 -0
  13. package/dist/features/categories/repository/categories.repository.js +11 -0
  14. package/dist/features/categories/schemas/firestore.d.ts +2 -0
  15. package/dist/features/categories/schemas/firestore.js +1 -0
  16. package/dist/features/layout/AppLayoutShell.js +1 -1
  17. package/dist/features/products/components/ProductGrid.js +2 -2
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/seed/categories-seed-data.d.ts +1 -0
  21. package/dist/seed/categories-seed-data.js +10 -4
  22. package/dist/seed/index.d.ts +1 -1
  23. package/dist/seed/index.js +1 -1
  24. package/dist/seed/orders-seed-data.js +8 -8
  25. package/dist/seed/products-standard-seed-data.js +16 -15
  26. package/dist/seed/site-settings-seed-data.js +1 -0
  27. package/dist/styles.css +9 -1
  28. package/dist/tailwind-utilities.css +1 -1
  29. package/dist/ui/components/PaginatedSelect.js +10 -1
  30. package/dist/ui/components/PaginatedSelect.style.css +8 -0
  31. package/package.json +1 -1
@@ -4,7 +4,10 @@ import { categoriesRepository } from "../../../../repositories";
4
4
  import { CATEGORIES_FEATURED_LIMIT, CATEGORIES_MENU_LIMIT, CATEGORIES_SITEMAP_LIMIT } from "../../../shared/features/categories/config";
5
5
  /** Full category document by slug — deduped per request via React.cache(). */
6
6
  export const getCategoryForDetail = cache(async (slug) => {
7
- return (await categoriesRepository.getCategoryBySlug(slug).catch(() => undefined)) ?? null;
7
+ const category = (await categoriesRepository.getCategoryBySlug(slug).catch(() => undefined)) ?? null;
8
+ if (category)
9
+ void categoriesRepository.incrementViewCount(category.id);
10
+ return category;
8
11
  });
9
12
  /** Flat list of all categories at tier 1 (roots) — for nav/sitemap. */
10
13
  export const listRootCategories = cache(async () => {
@@ -11,7 +11,11 @@ import { PRODUCT_FIELDS } from "../../../../constants/field-names";
11
11
  // ---------------------------------------------------------------------------
12
12
  /** Fetch a single product by slug or id, deduped per request. */
13
13
  export const getProductForDetail = cache(async (slugOrId) => {
14
- return (await productRepository.findByIdOrSlug(slugOrId).catch(() => undefined)) ?? null;
14
+ const product = (await productRepository.findByIdOrSlug(slugOrId).catch(() => undefined)) ?? null;
15
+ // Fire-and-forget viewCount increment — must not affect response time
16
+ if (product)
17
+ void productRepository.incrementViewCount(product.id);
18
+ return product;
15
19
  });
16
20
  /** Fetch the first page of approved reviews for a product, deduped per request. */
17
21
  export const getReviewsForProduct = cache(async (productId) => {
@@ -23,6 +23,7 @@ export interface AdminAnalyticsResult {
23
23
  revenue: number;
24
24
  orders: number;
25
25
  mainImage: string;
26
+ viewCount: number;
26
27
  }>;
27
28
  }
28
29
  export declare function runAdminAnalytics(_input: void, ctx: JobContext): Promise<AdminAnalyticsResult>;
@@ -82,6 +82,7 @@ export async function runAdminAnalytics(_input, ctx) {
82
82
  const topProductsWithImages = topProducts.map((p, i) => ({
83
83
  ...p,
84
84
  mainImage: topProductDocs[i]?.mainImage ?? "",
85
+ viewCount: topProductDocs[i]?.viewCount ?? 0,
85
86
  }));
86
87
  return {
87
88
  summary: {
@@ -424,6 +424,7 @@ export declare const CATEGORY_FIELDS: {
424
424
  readonly BUNDLE_STOCK_STATUS: "bundleStockStatus";
425
425
  readonly BUNDLE_QUERY_RESOLVED_AT: "bundleQueryResolvedAt";
426
426
  readonly DISPLAY: "display";
427
+ readonly VIEW_COUNT: "viewCount";
427
428
  readonly CREATED_AT: "createdAt";
428
429
  readonly UPDATED_AT: "updatedAt";
429
430
  readonly CATEGORY_TYPE_VALUES: {
@@ -465,6 +465,7 @@ export const CATEGORY_FIELDS = {
465
465
  BUNDLE_STOCK_STATUS: "bundleStockStatus",
466
466
  BUNDLE_QUERY_RESOLVED_AT: "bundleQueryResolvedAt",
467
467
  DISPLAY: "display",
468
+ VIEW_COUNT: "viewCount",
468
469
  CREATED_AT: "createdAt",
469
470
  UPDATED_AT: "updatedAt",
470
471
  CATEGORY_TYPE_VALUES: {
@@ -3,6 +3,7 @@ import type { AnalyticsTopProduct } from "../../types";
3
3
  export interface AdminTopProductsTableLabels {
4
4
  title?: string;
5
5
  orders?: string;
6
+ views?: string;
6
7
  view?: string;
7
8
  revenue?: string;
8
9
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Div, Heading, Row, Text } from "../../../../ui";
3
3
  export function AdminTopProductsTable({ products, labels = {}, formatRevenue = (n) => String(n), renderProductLink, className = "", }) {
4
- return (_jsxs(Div, { className: `border border-neutral-200 dark:border-neutral-800 sm:p-[1.5rem] ${className}`, rounded: "xl", padding: "md", surface: "default", children: [labels.title && (_jsx(Heading, { level: 3, className: "mb-4", size: "base", weight: "semibold", children: labels.title })), _jsx(Div, { children: products.map((product) => (_jsxs(Row, { className: "border-b last:border-b-0 border-neutral-100 dark:border-neutral-800", padding: "y-sm", align: "center", justify: "between", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "truncate", weight: "medium", children: product.title }), _jsxs(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "sm", children: [product.orders, " ", labels.orders ?? "orders"] })] }), _jsxs(Div, { className: "text-right ml-4", children: [_jsx(Text, { weight: "semibold", children: formatRevenue(product.revenue) }), renderProductLink?.(product)] })] }, product.productId))) })] }));
4
+ return (_jsxs(Div, { className: `border border-neutral-200 dark:border-neutral-800 sm:p-[1.5rem] ${className}`, rounded: "xl", padding: "md", surface: "default", children: [labels.title && (_jsx(Heading, { level: 3, className: "mb-4", size: "base", weight: "semibold", children: labels.title })), _jsx(Div, { children: products.map((product) => (_jsxs(Row, { className: "border-b last:border-b-0 border-neutral-100 dark:border-neutral-800", padding: "y-sm", align: "center", justify: "between", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "truncate", weight: "medium", children: product.title }), _jsxs(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "sm", children: [product.orders, " ", labels.orders ?? "orders"] })] }), product.viewCount != null && (_jsx(Div, { className: "text-right ml-4 hidden sm:block", children: _jsxs(Text, { size: "sm", color: "muted", children: [product.viewCount, " ", labels.views ?? "views"] }) })), _jsxs(Div, { className: "text-right ml-4", children: [_jsx(Text, { weight: "semibold", children: formatRevenue(product.revenue) }), renderProductLink?.(product)] })] }, product.productId))) })] }));
5
5
  }
@@ -373,6 +373,7 @@ export interface SiteSettingsDocument {
373
373
  auction?: boolean;
374
374
  "pre-order"?: boolean;
375
375
  "prize-draw"?: boolean;
376
+ bundle?: boolean;
376
377
  classified?: boolean;
377
378
  "digital-code"?: boolean;
378
379
  live?: boolean;
@@ -213,6 +213,7 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
213
213
  auction: true,
214
214
  "pre-order": true,
215
215
  "prize-draw": true,
216
+ bundle: true,
216
217
  classified: true,
217
218
  "digital-code": true,
218
219
  live: true,
@@ -44,6 +44,7 @@ export interface AnalyticsTopProduct {
44
44
  title: string;
45
45
  revenue: number;
46
46
  orders: number;
47
+ viewCount?: number;
47
48
  mainImage?: string;
48
49
  }
49
50
  export interface AnalyticsSummary {
@@ -55,6 +55,7 @@ export declare class CategoriesRepository extends BaseRepository<CategoryDocumen
55
55
  * SB-UNI-B — derive the canonical `sublisting-{slug}` ID from a
56
56
  * human-entered category name.
57
57
  */
58
+ incrementViewCount(categoryId: string): Promise<void>;
58
59
  generateSublistingId(name: string): string;
59
60
  }
60
61
  export declare const categoriesRepository: CategoriesRepository;
@@ -453,6 +453,17 @@ export class CategoriesRepository extends BaseRepository {
453
453
  * SB-UNI-B — derive the canonical `sublisting-{slug}` ID from a
454
454
  * human-entered category name.
455
455
  */
456
+ async incrementViewCount(categoryId) {
457
+ try {
458
+ await this.db
459
+ .collection(this.collection)
460
+ .doc(categoryId)
461
+ .update({ [CATEGORY_FIELDS.VIEW_COUNT]: increment(1) });
462
+ }
463
+ catch {
464
+ // View analytics must not break category read path.
465
+ }
466
+ }
456
467
  generateSublistingId(name) {
457
468
  const base = name
458
469
  .toLowerCase()
@@ -102,6 +102,7 @@ export interface CategoryDocument extends BaseDocument {
102
102
  */
103
103
  subtreeSize: number;
104
104
  metrics: CategoryDocumentMetrics;
105
+ viewCount?: number;
105
106
  isFeatured: boolean;
106
107
  featuredPriority?: number;
107
108
  /** @deprecated Use categoryType === "brand". Will be removed once seed + admin form fully migrate. */
@@ -240,6 +241,7 @@ export declare const CATEGORY_FIELDS: {
240
241
  readonly FEATURED_PRIORITY: "featuredPriority";
241
242
  readonly SEO: "seo";
242
243
  readonly DISPLAY: "display";
244
+ readonly VIEW_COUNT: "viewCount";
243
245
  readonly IS_ACTIVE: "isActive";
244
246
  readonly IS_SEARCHABLE: "isSearchable";
245
247
  readonly CATEGORY_TYPE: "categoryType";
@@ -176,6 +176,7 @@ export const CATEGORY_FIELDS = {
176
176
  FEATURED_PRIORITY: "featuredPriority",
177
177
  SEO: "seo",
178
178
  DISPLAY: "display",
179
+ VIEW_COUNT: "viewCount",
179
180
  IS_ACTIVE: "isActive",
180
181
  IS_SEARCHABLE: "isSearchable",
181
182
  CATEGORY_TYPE: "categoryType",
@@ -164,5 +164,5 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
164
164
  opacity: darkBackground.overlay?.opacity ?? 0,
165
165
  },
166
166
  };
167
- return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onTourStart: onTourStart, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot)] }), eventBannerSlot, _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 flex flex-col ${hasBottomActions ? "mb-28" : "mb-16"} md:mb-0`, children: _jsx(Div, { padding: "y-lg", className: `flex-1 ${contentClassName ?? "w-full px-5 md:px-6 lg:px-8"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
167
+ return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onTourStart: onTourStart, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot)] }), eventBannerSlot, _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 flex flex-col ${hasBottomActions ? "mb-28" : "mb-16"} md:mb-0`, children: _jsx(Div, { padding: "y-lg", className: `flex-1 ${contentClassName ?? "mx-auto w-full max-w-screen-xl px-5 md:px-6 lg:px-8"}`, children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
168
168
  }
@@ -91,10 +91,10 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
91
91
  const MAX_CHIPS = 3;
92
92
  const visible = categories.slice(0, MAX_CHIPS);
93
93
  const overflow = Math.max(0, categories.length - MAX_CHIPS);
94
- return (_jsxs(Row, { className: "mt-1", gap: "xs", wrap: true, children: [visible.map((name) => (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", className: "gap-[0.125rem] text-[10px] truncate max-w-[100px]", rounded: "full", surface: "subtle", color: "muted", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), name] }, name))), overflow > 0 && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", weight: "medium", className: "text-[10px]", rounded: "full", surface: "subtle", color: "muted", title: categories.slice(MAX_CHIPS).join(", "), children: ["+", overflow] })), product.brand && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", gap: "2xs", className: "text-[10px] truncate max-w-[90px]", rounded: "full", surface: "subtle", color: "muted", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] }));
94
+ return (_jsxs(Row, { className: "mt-1", gap: "xs", wrap: true, children: [visible.map((name) => (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", className: "gap-[0.125rem] text-[10px]", rounded: "full", surface: "subtle", color: "muted", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), name] }, name))), overflow > 0 && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", weight: "medium", className: "text-[10px]", rounded: "full", surface: "subtle", color: "muted", title: categories.slice(MAX_CHIPS).join(", "), children: ["+", overflow] })), product.brand && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", gap: "2xs", className: "text-[10px]", rounded: "full", surface: "subtle", color: "muted", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] }));
95
95
  })(), (() => {
96
96
  const seller = safeDisplayName(product.storeName, "");
97
- return seller ? (_jsxs(Text, { className: "mt-0.5 text-[11px] truncate", color: "faint", children: ["by ", seller] })) : null;
97
+ return seller ? (_jsxs(Text, { className: "mt-0.5 text-[11px]", color: "faint", children: ["by ", seller] })) : null;
98
98
  })(), product.rating !== undefined && (_jsxs(Row, { className: "mt-1", gap: "xs", children: [_jsx(Span, { className: CLS_STAR, children: "\u2605" }), _jsxs(Span, { className: "text-[11px]", color: "muted", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto", padding: "t-xs", children: [_jsxs(Row, { align: "baseline", gap: "sm", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary dark:text-primary-400", children: formatCurrency(product.price, getDefaultCurrency()) }), product.originalPrice && product.originalPrice > product.price && (_jsx(Span, { size: "xs", className: "line-through", color: "faint", children: formatCurrency(product.originalPrice, getDefaultCurrency()) }))] }), (() => {
99
99
  const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
100
100
  if (stock === undefined || isAuction)
package/dist/index.d.ts CHANGED
@@ -609,7 +609,7 @@ export { blogPostsSeedData } from "./seed/index";
609
609
  export { carouselSlidesSeedData } from "./seed/index";
610
610
  export { carouselsSeedData } from "./seed/index";
611
611
  export { cartsSeedData } from "./seed/index";
612
- export { categoriesSeedData } from "./seed/index";
612
+ export { categoriesSeedData, categoriesP1SeedData } from "./seed/index";
613
613
  export { couponsSeedData } from "./seed/index";
614
614
  export type { CouponUsageSeedRecord } from "./seed/index";
615
615
  export { couponUsageSeedData } from "./seed/index";
package/dist/index.js CHANGED
@@ -1236,7 +1236,7 @@ export { cartsSeedData } from "./seed/index";
1236
1236
  // [SERVER-ONLY]-Server-only â€" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
1237
1237
  // SB-UNI-C â€" brandsSeedData merged into categoriesSeedData with categoryType:"brand".
1238
1238
  // categoriesSeedData - Model for categories seed data.
1239
- export { categoriesSeedData } from "./seed/index";
1239
+ export { categoriesSeedData, categoriesP1SeedData } from "./seed/index";
1240
1240
  // [SERVER-ONLY]-Server-only â€" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
1241
1241
  // couponsSeedData - Model for coupons seed data.
1242
1242
  export { couponsSeedData } from "./seed/index";
@@ -1,2 +1,3 @@
1
1
  import type { CategoryDocument } from "../features/categories/schemas";
2
2
  export declare const categoriesSeedData: Partial<CategoryDocument>[];
3
+ export declare const categoriesP1SeedData: Partial<CategoryDocument>[];
@@ -2206,8 +2206,14 @@ const bundleRows = [
2206
2206
  },
2207
2207
  ];
2208
2208
  export const categoriesSeedData = [
2209
- ...rawCategories.map((c) => ({ ancestors: [], ...c, createdByType: "admin" })),
2210
- ...sublistingRows.map((s) => ({ ancestors: [], ...s, createdByType: "admin" })),
2211
- ...brandRows.map((b) => ({ ancestors: [], ...b, createdByType: "admin" })),
2212
- ...bundleRows.map((bundle) => ({ ancestors: [], ...bundle, createdByType: "admin" })),
2209
+ ...rawCategories.map((c) => ({ viewCount: 0, ancestors: [], ...c, createdByType: "admin" })),
2210
+ ...sublistingRows.map((s) => ({ viewCount: 0, ancestors: [], ...s, createdByType: "admin" })),
2211
+ ...brandRows.map((b) => ({ viewCount: 0, ancestors: [], ...b, createdByType: "admin" })),
2212
+ ...bundleRows.map((bundle) => ({ viewCount: 0, ancestors: [], ...bundle, createdByType: "admin" })),
2213
+ ];
2214
+ // P-1 default seed: excludes bundle rows (FEATURE_BUNDLES is off in P-1)
2215
+ export const categoriesP1SeedData = [
2216
+ ...rawCategories.map((c) => ({ viewCount: 0, ancestors: [], ...c, createdByType: "admin" })),
2217
+ ...sublistingRows.map((s) => ({ viewCount: 0, ancestors: [], ...s, createdByType: "admin" })),
2218
+ ...brandRows.map((b) => ({ viewCount: 0, ancestors: [], ...b, createdByType: "admin" })),
2213
2219
  ];
@@ -44,7 +44,7 @@ export { sessionsSeedData, SESSION_COLLECTION } from "./sessions-seed-data";
44
44
  export { addressesSeedData } from "./addresses-seed-data";
45
45
  export { storesSeedData } from "./stores-seed-data";
46
46
  export { storeAddressesSeedData } from "./store-addresses-seed-data";
47
- export { categoriesSeedData } from "./categories-seed-data";
47
+ export { categoriesSeedData, categoriesP1SeedData } from "./categories-seed-data";
48
48
  export { productsStandardSeedData } from "./products-standard-seed-data";
49
49
  export { productsAuctionsSeedData } from "./products-auctions-seed-data";
50
50
  export { productsPreordersSeedData } from "./products-preorders-seed-data";
@@ -32,7 +32,7 @@ export { sessionsSeedData, SESSION_COLLECTION } from "./sessions-seed-data";
32
32
  export { addressesSeedData } from "./addresses-seed-data";
33
33
  export { storesSeedData } from "./stores-seed-data";
34
34
  export { storeAddressesSeedData } from "./store-addresses-seed-data";
35
- export { categoriesSeedData } from "./categories-seed-data";
35
+ export { categoriesSeedData, categoriesP1SeedData } from "./categories-seed-data";
36
36
  export { productsStandardSeedData } from "./products-standard-seed-data";
37
37
  export { productsAuctionsSeedData } from "./products-auctions-seed-data";
38
38
  export { productsPreordersSeedData } from "./products-preorders-seed-data";
@@ -66,7 +66,7 @@ const _rawOrdersSeedData = [
66
66
  currency: "INR",
67
67
  status: "delivered",
68
68
  paymentStatus: "paid",
69
- paymentMethod: "cod",
69
+ paymentMethod: "cash",
70
70
  paymentId: "pay-yugi-kaiba-001",
71
71
  shippingAddress: "addr-yugi-home",
72
72
  trackingNumber: "JP-0001-KAIBA-DELIVERED",
@@ -91,7 +91,7 @@ const _rawOrdersSeedData = [
91
91
  currency: "INR",
92
92
  status: "shipped",
93
93
  paymentStatus: "paid",
94
- paymentMethod: "online",
94
+ paymentMethod: "cash",
95
95
  paymentId: "pay-yugi-kaiba-002",
96
96
  shippingAddress: "addr-yugi-home",
97
97
  trackingNumber: "JP-0002-KAIBA-SHIPPED",
@@ -115,7 +115,7 @@ const _rawOrdersSeedData = [
115
115
  currency: "INR",
116
116
  status: "processing",
117
117
  paymentStatus: "paid",
118
- paymentMethod: "cod",
118
+ paymentMethod: "cash",
119
119
  paymentId: "pay-yugi-kaiba-003",
120
120
  shippingAddress: "addr-yugi-home",
121
121
  orderDate: daysAgo(35),
@@ -136,7 +136,7 @@ const _rawOrdersSeedData = [
136
136
  currency: "INR",
137
137
  status: "pending",
138
138
  paymentStatus: "pending",
139
- paymentMethod: "online",
139
+ paymentMethod: "cash",
140
140
  paymentId: "pay-yugi-kaiba-004",
141
141
  shippingAddress: "addr-yugi-home",
142
142
  orderDate: daysAgo(30),
@@ -160,7 +160,7 @@ const _rawOrdersSeedData = [
160
160
  currency: "INR",
161
161
  status: "delivered",
162
162
  paymentStatus: "paid",
163
- paymentMethod: "online",
163
+ paymentMethod: "cash",
164
164
  paymentId: "pay-yugi-admin-001",
165
165
  shippingAddress: "addr-yugi-home",
166
166
  trackingNumber: "IN-0001-ADMIN-DELIVERED",
@@ -188,7 +188,7 @@ const _rawOrdersSeedData = [
188
188
  currency: "INR",
189
189
  status: "delivered",
190
190
  paymentStatus: "paid",
191
- paymentMethod: "online",
191
+ paymentMethod: "cash",
192
192
  paymentId: "pay-admin-kaiba-001",
193
193
  shippingAddress: "addr-letitrip-hq",
194
194
  trackingNumber: "JP-0010-KAIBA-DELIVERED",
@@ -216,7 +216,7 @@ const _rawOrdersSeedData = [
216
216
  currency: "INR",
217
217
  status: "delivered",
218
218
  paymentStatus: "paid",
219
- paymentMethod: "online",
219
+ paymentMethod: "cash",
220
220
  paymentId: "pay-kaiba-admin-001",
221
221
  shippingAddress: "addr-kaiba-mansion",
222
222
  trackingNumber: "IN-0005-ADMIN-DELIVERED",
@@ -282,7 +282,7 @@ for (let i = _rawOrdersSeedData.length; i < 50; i++) {
282
282
  currency: "INR",
283
283
  status: status,
284
284
  paymentStatus: (paymentStatuses[status] ?? "pending"),
285
- paymentMethod: i % 2 === 0 ? "cod" : "online",
285
+ paymentMethod: "cash",
286
286
  shippingAddress: combo.addressId,
287
287
  orderDate: daysAgo(daysBack),
288
288
  shippingDate: status === "shipped" || status === "delivered" ? daysAgo(daysBack - 5) : undefined,
@@ -19,6 +19,7 @@ function withTokens(p) {
19
19
  return {
20
20
  tags: [],
21
21
  featured: false,
22
+ viewCount: 0,
22
23
  ...p,
23
24
  searchTokens: buildSearchTokens(p.title, p.description, p.brand, p.brandSlug, p.categoryNames, p.tags, p.features, p.condition, p.card?.setName, p.card?.cardNumber, p.grading?.service, p.specifications?.map((s) => `${s.name} ${s.value}`)),
24
25
  };
@@ -269,7 +270,7 @@ const _rawProductsStandardSeedData = [
269
270
  isSold: false,
270
271
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/cropped/${CARD_IDS.darkMagician}.jpg`),
271
272
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/cropped/${CARD_IDS.darkMagician}.jpg`)],
272
- status: PRODUCT_FIELDS.STATUS_VALUES.DRAFT,
273
+ status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
273
274
  condition: PRODUCT_FIELDS.CONDITION_VALUES.NEW,
274
275
  storeId: "store-letitrip-official",
275
276
  storeName: "LetItRip Official",
@@ -410,7 +411,7 @@ const _kaibaSinglesSeedData = [
410
411
  isSold: false,
411
412
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.kuriboh}.jpg`),
412
413
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.kuriboh}.jpg`)],
413
- status: PRODUCT_FIELDS.STATUS_VALUES.DRAFT,
414
+ status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
414
415
  condition: PRODUCT_FIELDS.CONDITION_VALUES.USED,
415
416
  storeId: "store-kaiba-corp-cards",
416
417
  storeName: "Kaiba Corp Card Vault",
@@ -474,9 +475,9 @@ const _kaibaSinglesSeedData = [
474
475
  brand: "Konami",
475
476
  price: 899900,
476
477
  currency: "INR",
477
- stockQuantity: 1,
478
- availableQuantity: 0,
479
- isSold: true,
478
+ stockQuantity: 3,
479
+ availableQuantity: 3,
480
+ isSold: false,
480
481
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.darkMagicianGirl}.jpg`),
481
482
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.darkMagicianGirl}.jpg`)],
482
483
  status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
@@ -497,8 +498,8 @@ const _kaibaSinglesSeedData = [
497
498
  brand: "Konami",
498
499
  price: 1299900,
499
500
  currency: "INR",
500
- stockQuantity: 0,
501
- availableQuantity: 0,
501
+ stockQuantity: 5,
502
+ availableQuantity: 5,
502
503
  isSold: false,
503
504
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.chaosEmperorDragon}.jpg`),
504
505
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.chaosEmperorDragon}.jpg`)],
@@ -548,7 +549,7 @@ const _kaibaSinglesSeedData = [
548
549
  isSold: false,
549
550
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.cyberDragon}.jpg`),
550
551
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.cyberDragon}.jpg`)],
551
- status: PRODUCT_FIELDS.STATUS_VALUES.DRAFT,
552
+ status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
552
553
  condition: PRODUCT_FIELDS.CONDITION_VALUES.NEW,
553
554
  storeId: "store-kaiba-corp-cards",
554
555
  storeName: "Kaiba Corp Card Vault",
@@ -775,9 +776,9 @@ const _kaibaTrapSpellsSeedData = [
775
776
  brand: "Konami",
776
777
  price: 999900,
777
778
  currency: "INR",
778
- stockQuantity: 1,
779
- availableQuantity: 0,
780
- isSold: true,
779
+ stockQuantity: 3,
780
+ availableQuantity: 3,
781
+ isSold: false,
781
782
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.mirrorForce}.jpg`),
782
783
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.mirrorForce}.jpg`)],
783
784
  status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
@@ -803,7 +804,7 @@ const _kaibaTrapSpellsSeedData = [
803
804
  isSold: false,
804
805
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.mysticalSpaceTyphoon}.jpg`),
805
806
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/${CARD_IDS.mysticalSpaceTyphoon}.jpg`)],
806
- status: PRODUCT_FIELDS.STATUS_VALUES.DRAFT,
807
+ status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
807
808
  condition: PRODUCT_FIELDS.CONDITION_VALUES.USED,
808
809
  storeId: "store-kaiba-corp-cards",
809
810
  storeName: "Kaiba Corp Card Vault",
@@ -984,8 +985,8 @@ const _kaibaSealedExtraDeckSeedData = [
984
985
  brand: "Konami",
985
986
  price: 999900,
986
987
  currency: "INR",
987
- stockQuantity: 0,
988
- availableQuantity: 0,
988
+ stockQuantity: 5,
989
+ availableQuantity: 5,
989
990
  isSold: false,
990
991
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/cropped/${CARD_IDS.sliferTheSkyDragon}.jpg`),
991
992
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/cropped/${CARD_IDS.sliferTheSkyDragon}.jpg`)],
@@ -1196,7 +1197,7 @@ const _kaibaSealedExtraDeckSeedData = [
1196
1197
  isSold: false,
1197
1198
  mainImage: seedExtMedia(`https://images.ygoprodeck.com/images/cards/cropped/${CARD_IDS.darkMagician}.jpg`),
1198
1199
  images: [seedExtMedia(`https://images.ygoprodeck.com/images/cards/cropped/${CARD_IDS.darkMagician}.jpg`)],
1199
- status: PRODUCT_FIELDS.STATUS_VALUES.DRAFT,
1200
+ status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
1200
1201
  condition: PRODUCT_FIELDS.CONDITION_VALUES.NEW,
1201
1202
  storeId: "store-kaiba-corp-cards",
1202
1203
  storeName: "Kaiba Corp Card Vault",
@@ -390,6 +390,7 @@ export const siteSettingsSeedData = {
390
390
  auction: false,
391
391
  "pre-order": false,
392
392
  "prize-draw": false,
393
+ bundle: false,
393
394
  classified: false,
394
395
  "digital-code": false,
395
396
  live: false,