@mohasinac/appkit 3.2.1 → 3.2.3
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/server/features/categories/data.js +4 -1
- package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +1 -0
- package/dist/_internal/server/jobs/core/adminAnalytics.js +1 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/features/admin/components/analytics/AdminTopProductsTable.d.ts +1 -0
- package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
- package/dist/features/admin/schemas/firestore.d.ts +1 -0
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/admin/types/index.d.ts +1 -0
- package/dist/features/categories/repository/categories.repository.d.ts +1 -0
- package/dist/features/categories/repository/categories.repository.js +11 -0
- package/dist/features/categories/schemas/firestore.d.ts +2 -0
- package/dist/features/categories/schemas/firestore.js +1 -0
- package/dist/seed/site-settings-seed-data.js +1 -0
- 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
|
-
|
|
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 () => {
|
|
@@ -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: {
|
|
@@ -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
|
}
|
|
@@ -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";
|