Files
mintel.me/apps/web/src/components/Analytics.tsx
Marc Mintel b15c8408ff
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 14s
Build & Deploy / 🧪 QA (push) Failing after 1m48s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(blog): optimize component share logic, typography, and modal layouts
2026-02-22 11:41:28 +01:00

145 lines
4.2 KiB
TypeScript

"use client";
import React, { useEffect, Suspense } from "react";
import { useSafePathname, useSafeSearchParams } from "./analytics/useSafePathname";
import { ScrollDepthTracker } from "./analytics/ScrollDepthTracker";
import { getDefaultAnalytics } from "../utils/analytics";
import { getDefaultErrorTracking } from "../utils/error-tracking";
const AnalyticsInner: React.FC = () => {
const pathname = useSafePathname();
const searchParams = useSafeSearchParams();
// Track pageviews on route change
useEffect(() => {
if (!pathname) return;
const analytics = getDefaultAnalytics();
const url = `${pathname}${searchParams?.size ? `?${searchParams.toString()}` : ""}`;
analytics.page(url);
}, [pathname, searchParams]);
useEffect(() => {
const analytics = getDefaultAnalytics();
const errorTracking = getDefaultErrorTracking();
// Track page load performance
const trackPageLoad = () => {
// ... existing implementation ...
const perfData = performance.getEntriesByType(
"navigation",
)[0] as PerformanceNavigationTiming;
if (
perfData &&
typeof perfData.loadEventEnd === "number" &&
typeof perfData.startTime === "number"
) {
const loadTime = perfData.loadEventEnd - perfData.startTime;
analytics.trackPageLoad(
loadTime,
window.location.pathname,
navigator.userAgent,
);
}
};
// Track outbound links
const trackOutboundLinks = () => {
document.querySelectorAll('a[href^="http"]').forEach((link) => {
const anchor = link as HTMLAnchorElement;
if (!anchor.href.includes(window.location.hostname)) {
anchor.addEventListener("click", () => {
analytics.trackOutboundLink(
anchor.href,
anchor.textContent?.trim() || "unknown",
);
});
}
});
};
// Track search
const trackSearch = () => {
const searchInput = document.querySelector(
'input[type="search"]',
) as HTMLInputElement;
if (searchInput) {
const handleSearch = (e: Event) => {
const target = e.target as HTMLInputElement;
if (target.value) {
analytics.trackSearch(target.value, window.location.pathname);
}
};
searchInput.addEventListener("search", handleSearch);
return () => searchInput.removeEventListener("search", handleSearch);
}
};
// Global error handler for error tracking
const handleGlobalError = (event: ErrorEvent) => {
errorTracking.captureException(event.error || event.message);
};
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
errorTracking.captureException(event.reason);
};
window.addEventListener("error", handleGlobalError);
window.addEventListener("unhandledrejection", handleUnhandledRejection);
// Initial load tracking
if (document.readyState === "complete") {
trackPageLoad();
trackOutboundLinks();
const cleanupSearch = trackSearch();
return () => {
if (cleanupSearch) cleanupSearch();
window.removeEventListener("error", handleGlobalError);
window.removeEventListener(
"unhandledrejection",
handleUnhandledRejection,
);
};
} else {
window.addEventListener("load", () => {
trackPageLoad();
trackOutboundLinks();
// search tracking might need to wait for hydration/render
});
// Fallback/standard cleanup
return () => {
window.removeEventListener("error", handleGlobalError);
window.removeEventListener(
"unhandledrejection",
handleUnhandledRejection,
);
};
}
}, []);
const analytics = getDefaultAnalytics();
const adapter = analytics.getAdapter();
const scriptTag = adapter.getScriptTag ? adapter.getScriptTag() : null;
return (
<>
<ScrollDepthTracker />
{scriptTag && (
<div
dangerouslySetInnerHTML={{ __html: scriptTag }}
style={{ display: "none" }}
/>
)}
</>
);
};
export const Analytics: React.FC = () => {
return (
<Suspense fallback={null}>
<AnalyticsInner />
</Suspense>
);
};