feat(analytics): persona logging upgrade and Sentry system config

Add client-logging SDK, expanded event taxonomy, API observability, admin domain events UI, and move SENTRY_DSN to HQ system settings with @sentry/node bootstrap after config preload.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 22:30:02 +08:00
parent ab10431001
commit 89fd333702
91 changed files with 1805 additions and 136 deletions
+1
View File
@@ -13,6 +13,7 @@
},
"dependencies": {
"@babel/runtime": "^7.24.4",
"@dukang/client-logging": "workspace:*",
"@dukang/shared-types": "workspace:*",
"@dukang/shared-ui": "workspace:*",
"@dukang/weixin-sdk": "workspace:*",
+22
View File
@@ -0,0 +1,22 @@
import { createUserTracker, getSessionId } from '@dukang/client-logging';
import { API_BASE, CLIENT_APP, getToken } from './api';
const tracker = createUserTracker({
apiBase: API_BASE,
clientApp: CLIENT_APP,
getToken,
});
export { getSessionId };
export function track(eventName: string, params?: Record<string, unknown>) {
tracker.track(eventName, params);
}
export function trackPageView(eventName: string, params?: Record<string, unknown>) {
tracker.trackPageView(eventName, params);
}
export function initUserAnalytics() {
tracker.trackSessionStart();
}
+11
View File
@@ -0,0 +1,11 @@
import { useEffect, useRef } from 'react';
import { trackPageView } from './analytics';
export function usePageView(eventName: string, params?: Record<string, unknown>) {
const fired = useRef(false);
useEffect(() => {
if (fired.current) return;
fired.current = true;
trackPageView(eventName, params);
}, [eventName, params]);
}
+5
View File
@@ -33,6 +33,7 @@ import {
DEFAULT_SHARE_TITLE,
toWeappShareMessage,
} from '../../lib/wechat-share';
import { trackPageView } from '../../lib/analytics';
type Product = {
id: string;
name: string;
@@ -79,6 +80,10 @@ export default function HomePage() {
const scrollLockTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastScrollSyncAtRef = useRef(0);
useEffect(() => {
trackPageView('home_view', { pagePath: '/pages/home/index', cityCode });
}, [cityCode]);
const loadMiniHome = useCallback(() => {
return request<{ miniHome?: MiniHomeConfig }>('/common/client-config')
.then((cfg) => {
@@ -15,6 +15,7 @@ import {
DEFAULT_SHARE_TITLE,
toWeappShareMessage,
} from '../../lib/wechat-share';
import { usePageView } from '../../lib/usePageView';
const isWeapp = process.env.TARO_ENV === 'weapp';
@@ -74,6 +75,7 @@ function fullReceiverAddress(order: OrderDetail) {
export default function OrderDetailPage() {
const router = useRouter();
const orderId = router.params.id ?? '';
usePageView('order_detail_view', orderId ? { orderId } : undefined);
const [order, setOrder] = useState<OrderDetail | null>(null);
const [confirming, setConfirming] = useState(false);
@@ -5,6 +5,7 @@ import PageShell from '../../components/PageShell';
import SubPageHeader from '../../components/SubPageHeader';
import { request, toast } from '../../lib/api';
import { buildPayUrl } from '../../lib/checkout-nav';
import { usePageView } from '../../lib/usePageView';
import { ORDER_STATUS_LABELS } from '@dukang/shared-types';
@@ -54,6 +55,7 @@ type OrderRow = {
export default function OrdersPage() {
const router = useRouter();
const [tab, setTab] = useState(() => normalizeOrdersTab(router.params.tab as string));
usePageView('order_list_view', { tab });
const [orders, setOrders] = useState<OrderRow[]>([]);
const [loading, setLoading] = useState(true);
@@ -33,6 +33,7 @@ import {
toWeappShareMessage,
} from '../../lib/wechat-share';
import iconHome from '../../assets/tabbar/home.png';
import { usePageView } from '../../lib/usePageView';
type Product = ProductImageSource & {
id: string;
@@ -50,6 +51,10 @@ type Product = ProductImageSource & {
export default function ProductDetailPage() {
const router = useRouter();
const productId = router.params.id ?? '';
usePageView(
'product_detail_view',
productId ? { refType: 'PRODUCT', refId: productId, productId } : undefined,
);
const [product, setProduct] = useState<Product | null>(null);
const [headerSolid, setHeaderSolid] = useState(false);