/** Monitoring and analytics integration for TenderHub. */ import type { User } from "@supabase/supabase-js"; // Sentry configuration const getSentryDsn = () => process.env.NEXT_PUBLIC_SENTRY_DSN; const isSentryEnabled = () => false; // Disabled for now: !!getSentryDsn() && process.env.NEXT_PUBLIC_SENTRY_ENABLED !== "false"; // PostHog configuration const getPosthogKey = () => process.env.NEXT_PUBLIC_POSTHOG_KEY; const getPosthogHost = () => process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://app.posthog.com"; const isPosthogEnabled = () => false; // Disabled for now: !!getPosthogKey(); // eslint-disable-next-line @typescript-eslint/no-explicit-any let sentryModule: any = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any let posthogClient: any = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getSentry(): Promise { if (!isSentryEnabled()) return null; if (!sentryModule) { try { // @ts-ignore -- optional dependency, loaded at runtime sentryModule = await import("@sentry/nextjs"); } catch { console.warn("Sentry not installed"); return null; } } return sentryModule; } // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getPostHog(): Promise { if (!isPosthogEnabled()) return null; if (!posthogClient) { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any // @ts-ignore -- optional dependency, loaded at runtime const posthog = await import("posthog-js") as any; posthogClient = posthog.default.init(getPosthogKey()!, { api_host: getPosthogHost(), capture_pageview: true, capture_pageleave: true, persistence: "localStorage", loaded: (ph: { opt_out_capturing: () => void }) => { if (process.env.NODE_ENV === "development") { ph.opt_out_capturing(); } }, }); } catch { console.warn("PostHog not installed"); return null; } } return posthogClient; } // Error tracking export async function captureException(error: Error, context?: Record): Promise { console.error("[Error]", error, context); const sentry = await getSentry(); if (sentry) { if (context) { sentry.withScope((scope: any) => { Object.entries(context).forEach(([key, value]) => { scope.setExtra(key, value); }); scope.captureException(error); }); } else { sentry.captureException(error); } } } export async function captureMessage(message: string, level: "info" | "warning" | "error" = "info", context?: Record): Promise { console.log(`[${level.toUpperCase()}]`, message, context); const sentry = await getSentry(); if (sentry) { if (context) { sentry.withScope((scope: any) => { Object.entries(context).forEach(([key, value]) => { scope.setExtra(key, value); }); scope.captureMessage(message, level); }); } else { sentry.captureMessage(message, level); } } } // User identification export async function identifyUser(user: User, organizationId?: string): Promise { const posthog = await getPostHog(); if (posthog) { posthog.identify(user.id, { email: user.email, organizationId, }); } const sentry = await getSentry(); if (sentry) { sentry.setUser({ id: user.id, email: user.email, }); if (organizationId) { sentry.setTag("organizationId", organizationId); } } } export async function resetUser(): Promise { const posthog = await getPostHog(); if (posthog) { posthog.reset(); } const sentry = await getSentry(); if (sentry) { sentry.setUser(null); } } // Event tracking export async function trackEvent( eventName: string, properties?: Record ): Promise { console.log("[Analytics]", eventName, properties); const posthog = await getPostHog(); if (posthog) { posthog.capture(eventName, properties); } } // Product analytics events export const AnalyticsEvents = { // Auth USER_SIGNUP: "user_signup", USER_LOGIN: "user_login", USER_LOGOUT: "user_logout", // Tenders TENDER_UPLOADED: "tender_uploaded", TENDER_PROCESSING_STARTED: "tender_processing_started", TENDER_ANALYSIS_VIEWED: "tender_analysis_viewed", TENDER_UNLOCKED: "tender_unlocked", // Drafts DRAFT_GENERATED: "draft_generated", DRAFT_EXPORTED: "draft_exported", DRAFT_DOWNLOADED: "draft_downloaded", // Payments PAYMENT_INITIATED: "payment_initiated", PAYMENT_COMPLETED: "payment_completed", PAYMENT_FAILED: "payment_failed", // Company Profile PROFILE_COMPLETED: "profile_completed", PROFILE_UPDATED: "profile_updated", // Errors EXTRACTION_FAILED: "extraction_failed", DRAFT_GENERATION_FAILED: "draft_generation_failed", UPLOAD_FAILED: "upload_failed", } as const; // Breadcrumbs for debugging export async function addBreadcrumb( message: string, category?: string, data?: Record ): Promise { const sentry = await getSentry(); if (sentry) { sentry.addBreadcrumb({ message, category, data, level: "info", }); } } // Performance monitoring export async function startSpanSafe( name: string, op: string ): Promise<{ finish: () => void } | null> { const sentry = await getSentry(); if (!sentry) return null; // Use startSpan if available (Sentry v8+), otherwise no-op if (typeof sentry.startSpan === "function") { try { sentry.startSpan({ name, op }, () => {}); } catch { // Span API may not be available in all builds } } // Return a no-op handle for backward compatibility return { finish: () => {} }; } // Initialize monitoring on app start export async function initMonitoring(): Promise { if (isSentryEnabled()) { await getSentry(); console.log("[Monitoring] Sentry initialized"); } if (isPosthogEnabled()) { await getPostHog(); console.log("[Monitoring] PostHog initialized"); } }