From 0eeb63bbde16c5b428e895886321b86891c208c1 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Tue, 20 Jan 2026 20:13:29 -0300 Subject: [PATCH] feat(analytics): integrate Rybbit Analytics with error tracking and session replay - Added rewrites in next.config.mjs for Rybbit Analytics to bypass adblockers. - Implemented ErrorBoundary and ErrorTracking components in layout.tsx for enhanced error handling. - Included a script for analytics tracking in the root layout. - Updated NutritionGrid in product page to handle potential undefined nutrition data. --- app/components/ErrorBoundary.tsx | 78 ++++++++++++++++++++++++++++++++ app/components/ErrorTracking.tsx | 62 +++++++++++++++++++++++++ app/layout.tsx | 17 ++++++- app/produto/[barcode]/page.tsx | 2 +- next.config.mjs | 26 +++++++++++ package.json | 10 ++-- 6 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 app/components/ErrorBoundary.tsx create mode 100644 app/components/ErrorTracking.tsx diff --git a/app/components/ErrorBoundary.tsx b/app/components/ErrorBoundary.tsx new file mode 100644 index 0000000..c2e5030 --- /dev/null +++ b/app/components/ErrorBoundary.tsx @@ -0,0 +1,78 @@ +"use client"; + +import React, { Component, ErrorInfo, ReactNode } from "react"; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +/** + * Error Boundary para capturar erros React e enviar ao Rybbit + */ +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + // Enviar erro ao Rybbit se disponível + if (typeof window !== "undefined" && (window as any).rybbit) { + try { + (window as any).rybbit.track("error", { + message: error.message, + stack: error.stack, + componentStack: errorInfo.componentStack, + errorName: error.name, + timestamp: new Date().toISOString(), + }); + } catch (trackingError) { + console.error("[ErrorBoundary] Falha ao enviar erro ao Rybbit:", trackingError); + } + } + + console.error("[ErrorBoundary] Erro capturado:", error, errorInfo); + } + + render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+

+ Erro Inesperado +

+

+ {this.state.error?.message || "Algo deu errado. Por favor, recarregue a página."} +

+ +
+
+ ); + } + + return this.props.children; + } +} diff --git a/app/components/ErrorTracking.tsx b/app/components/ErrorTracking.tsx new file mode 100644 index 0000000..3d54c68 --- /dev/null +++ b/app/components/ErrorTracking.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useEffect } from "react"; + +/** + * Componente para configurar error tracking global do Rybbit + * Captura erros JavaScript não tratados e Promise rejections + */ +export function ErrorTracking() { + useEffect(() => { + // Handler para erros JavaScript não capturados + const handleError = (event: ErrorEvent) => { + if (typeof window !== "undefined" && (window as any).rybbit) { + try { + (window as any).rybbit.track("error", { + message: event.message, + filename: event.filename, + lineno: event.lineno, + colno: event.colno, + stack: event.error?.stack, + errorName: event.error?.name, + timestamp: new Date().toISOString(), + type: "javascript_error", + }); + } catch (trackingError) { + console.error("[ErrorTracking] Falha ao enviar erro ao Rybbit:", trackingError); + } + } + }; + + // Handler para Promise rejections não tratadas + const handleUnhandledRejection = (event: PromiseRejectionEvent) => { + if (typeof window !== "undefined" && (window as any).rybbit) { + try { + const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason)); + + (window as any).rybbit.track("error", { + message: error.message || String(event.reason), + stack: error.stack, + errorName: error.name || "UnhandledPromiseRejection", + timestamp: new Date().toISOString(), + type: "unhandled_promise_rejection", + }); + } catch (trackingError) { + console.error("[ErrorTracking] Falha ao enviar rejection ao Rybbit:", trackingError); + } + } + }; + + // Adicionar listeners + window.addEventListener("error", handleError); + window.addEventListener("unhandledrejection", handleUnhandledRejection); + + // Cleanup + return () => { + window.removeEventListener("error", handleError); + window.removeEventListener("unhandledrejection", handleUnhandledRejection); + }; + }, []); + + return null; +} diff --git a/app/layout.tsx b/app/layout.tsx index afeac94..d64983e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,10 @@ import "./globals.css"; import { Metadata } from "next"; import { Inter } from "next/font/google"; +import Script from "next/script"; import { ThemeProvider } from "@/app/providers"; +import { ErrorBoundary } from "@/app/components/ErrorBoundary"; +import { ErrorTracking } from "@/app/components/ErrorTracking"; // Use Inter como fallback já que é uma fonte similar à Geist const inter = Inter({ subsets: ["latin"] }); @@ -79,7 +82,19 @@ export default function RootLayout({ return ( - {children} + + + {children} + + + +