2026-01-16 13:28:50 +00:00
|
|
|
/**
|
|
|
|
|
* Monitor 3: User Analytics Service
|
|
|
|
|
* Tracks feature usage and user interactions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { apiClient } from './api/client';
|
|
|
|
|
import { logger } from '@/utils/logger';
|
|
|
|
|
|
2026-01-07 09:33:52 +00:00
|
|
|
export const analyticsService = {
|
2026-01-16 13:28:50 +00:00
|
|
|
/**
|
|
|
|
|
* Monitor 3: Record a user analytics event
|
|
|
|
|
* Tracks feature usage and user interactions
|
|
|
|
|
* @param eventName - Name of the event (e.g., 'feature_used', 'button_clicked')
|
|
|
|
|
* @param payload - Optional event data (e.g., { feature: 'search', query: '...' })
|
|
|
|
|
*/
|
|
|
|
|
recordEvent: async (eventName: string, payload?: Record<string, unknown>) => {
|
|
|
|
|
try {
|
|
|
|
|
// Send event to backend analytics endpoint
|
|
|
|
|
await apiClient.post('/analytics/events', {
|
|
|
|
|
event_name: eventName,
|
|
|
|
|
payload: payload || {},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Log in development for debugging
|
|
|
|
|
if (import.meta.env.DEV) {
|
|
|
|
|
logger.debug('[Analytics] Event recorded', {
|
|
|
|
|
event_name: eventName,
|
|
|
|
|
payload,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Don't throw errors for analytics - fail silently to avoid disrupting user experience
|
|
|
|
|
if (import.meta.env.DEV) {
|
|
|
|
|
logger.warn('[Analytics] Failed to record event', {
|
|
|
|
|
event_name: eventName,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
},
|
2026-01-07 09:33:52 +00:00
|
|
|
|
2026-01-26 13:12:17 +00:00
|
|
|
getGlobalStats: async (range: string = '30d') => {
|
|
|
|
|
try {
|
2026-02-15 15:21:20 +00:00
|
|
|
const response = await apiClient.get<{ data?: Record<string, unknown> }>('/analytics', {
|
2026-01-26 13:12:17 +00:00
|
|
|
params: { days: range.replace('d', '') }
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-15 15:21:20 +00:00
|
|
|
const data = response.data?.data ?? response.data;
|
|
|
|
|
if (!data || typeof data !== 'object') {
|
|
|
|
|
return {};
|
2026-01-26 13:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
2026-02-15 15:21:20 +00:00
|
|
|
total_tracks: data.total_tracks ?? 0,
|
|
|
|
|
total_plays: data.total_plays ?? 0,
|
|
|
|
|
total_revenue: data.total_revenue ?? 0,
|
|
|
|
|
followers: data.followers ?? 0,
|
|
|
|
|
profile_views: data.profile_views ?? 0,
|
|
|
|
|
trends: data.trends ?? { plays: 0, revenue: 0, followers: 0, views: 0 },
|
|
|
|
|
sparklines: data.sparklines ?? { plays: [0], revenue: [0], followers: [0], views: [0] },
|
2026-01-26 13:12:17 +00:00
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('[Analytics] Failed to fetch global stats', { error });
|
2026-02-15 15:21:20 +00:00
|
|
|
throw error;
|
2026-01-26 13:12:17 +00:00
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
},
|
2026-01-07 09:33:52 +00:00
|
|
|
|
2026-01-26 13:12:17 +00:00
|
|
|
getTopTracks: async (range: string = '30d') => {
|
|
|
|
|
try {
|
2026-02-15 15:21:20 +00:00
|
|
|
const response = await apiClient.get<{ data?: { tracks?: { top_tracks?: unknown[] } } }>('/analytics', {
|
2026-01-26 13:12:17 +00:00
|
|
|
params: { days: range.replace('d', '') }
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-15 15:21:20 +00:00
|
|
|
const data = response.data?.data ?? response.data;
|
|
|
|
|
const topTracks = (data as Record<string, unknown>)?.tracks as { top_tracks?: Array<{ id?: string; title?: string; plays?: number; play_count?: number; change?: number; revenue?: number }> } | undefined;
|
|
|
|
|
const tracks = topTracks?.top_tracks ?? [];
|
2026-01-26 13:12:17 +00:00
|
|
|
|
2026-02-15 15:21:20 +00:00
|
|
|
return tracks.map((t) => ({
|
|
|
|
|
id: t.id ?? '',
|
|
|
|
|
title: t.title ?? '',
|
|
|
|
|
plays: t.plays ?? t.play_count ?? 0,
|
|
|
|
|
change: t.change ?? 0,
|
|
|
|
|
revenue: t.revenue ?? 0,
|
|
|
|
|
}));
|
2026-01-26 13:12:17 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('[Analytics] Failed to fetch top tracks', { error });
|
2026-02-15 15:21:20 +00:00
|
|
|
throw error;
|
2026-01-26 13:12:17 +00:00
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
},
|
2026-01-07 09:33:52 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
getTrafficSources: async () => {
|
2026-02-15 15:21:20 +00:00
|
|
|
// Not implemented in backend - returns empty until backend supports it
|
|
|
|
|
return [];
|
2026-01-13 18:47:57 +00:00
|
|
|
},
|
2026-01-07 09:33:52 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
getDeviceBreakdown: async () => {
|
2026-02-15 15:21:20 +00:00
|
|
|
// Not implemented in backend - returns zeros until backend supports it
|
|
|
|
|
return { mobile: 0, desktop: 0 };
|
2026-01-13 18:47:57 +00:00
|
|
|
},
|
2026-01-07 09:33:52 +00:00
|
|
|
};
|