Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
130 lines
3.8 KiB
TypeScript
130 lines
3.8 KiB
TypeScript
import type {
|
|
ViewEventData,
|
|
EngagementEventData,
|
|
} from './types';
|
|
|
|
export interface BackendAnalyticsConfig {
|
|
apiBaseUrl: string;
|
|
appName: string;
|
|
enableDebugLogging?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Backend analytics client for server-side event tracking
|
|
* Uses fire-and-forget pattern to avoid blocking request processing
|
|
*/
|
|
export class BackendAnalyticsClient {
|
|
private config: Required<BackendAnalyticsConfig>;
|
|
|
|
constructor(config: BackendAnalyticsConfig) {
|
|
this.config = {
|
|
enableDebugLogging: false,
|
|
...config,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Track a view event (fire-and-forget)
|
|
*/
|
|
trackView(data: Omit<ViewEventData, 'app'>): void {
|
|
this.sendEvent('view', {
|
|
...data,
|
|
app: this.config.appName,
|
|
}).catch((error) => {
|
|
if (this.config.enableDebugLogging) {
|
|
console.error('[Analytics] Failed to track view:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Track an engagement event (fire-and-forget)
|
|
*/
|
|
trackEngagement(data: EngagementEventData): void {
|
|
this.sendEvent('engagement', data).catch((error) => {
|
|
if (this.config.enableDebugLogging) {
|
|
console.error('[Analytics] Failed to track engagement:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Track an API call event (specialized view event)
|
|
*/
|
|
trackApiCall(data: {
|
|
endpoint: string;
|
|
method: string;
|
|
userId?: string;
|
|
statusCode: number;
|
|
duration?: number;
|
|
ipAddress?: string;
|
|
}): void {
|
|
this.trackView({
|
|
contentId: `${data.method}:${data.endpoint}`,
|
|
contentType: 'post', // Using 'post' as generic content type for API calls
|
|
userId: data.userId,
|
|
sessionId: this.generateSessionId(),
|
|
duration: data.duration,
|
|
ipAddress: data.ipAddress,
|
|
deviceType: 'desktop', // Backend calls don't have device type
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Track a business event (specialized engagement event)
|
|
*/
|
|
trackBusinessEvent(data: {
|
|
userId: string;
|
|
eventType: 'message_sent' | 'message_received' | 'stream_started' | 'stream_ended' | 'payment_processed';
|
|
targetId: string;
|
|
metadata?: Record<string, unknown>;
|
|
}): void {
|
|
// Map business events to engagement metric types
|
|
const metricTypeMap: Record<typeof data.eventType, EngagementEventData['metricType']> = {
|
|
message_sent: 'comment', // Using comment as proxy for communication
|
|
message_received: 'comment',
|
|
stream_started: 'subscribe', // Using subscribe as proxy for stream engagement
|
|
stream_ended: 'subscribe',
|
|
payment_processed: 'purchase',
|
|
};
|
|
|
|
this.trackEngagement({
|
|
userId: data.userId,
|
|
metricType: metricTypeMap[data.eventType],
|
|
targetId: data.targetId,
|
|
targetType: 'content', // Generic target type for business events
|
|
metadata: {
|
|
...data.metadata,
|
|
businessEventType: data.eventType,
|
|
},
|
|
});
|
|
}
|
|
|
|
private async sendEvent(type: 'view' | 'engagement', data: ViewEventData | EngagementEventData): Promise<void> {
|
|
const endpoint = type === 'view' ? '/analytics/track/view' : '/analytics/track/engagement';
|
|
|
|
try {
|
|
const response = await fetch(`${this.config.apiBaseUrl}${endpoint}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(data),
|
|
signal: AbortSignal.timeout(5000), // 5 second timeout
|
|
});
|
|
|
|
if (!response.ok && this.config.enableDebugLogging) {
|
|
console.warn(`[Analytics] Failed to track ${type}: ${response.status} ${response.statusText}`);
|
|
}
|
|
} catch (error) {
|
|
// Silently fail in production, log in debug mode
|
|
if (this.config.enableDebugLogging) {
|
|
console.error(`[Analytics] Error tracking ${type}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
private generateSessionId(): string {
|
|
return `backend-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
}
|
|
}
|