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} + + + +