From 21f7aa7c406f97764b54243e51b984ac6ea16fae Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Sat, 11 Jan 2025 14:52:27 -0300 Subject: [PATCH] Implementando Rudderstack --- src/components/audio/AudioUploader.tsx | 6 ++ src/components/auth/LoginForm.tsx | 39 ++++++--- src/hooks/useErrorTracking.ts | 114 +++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 src/hooks/useErrorTracking.ts diff --git a/src/components/audio/AudioUploader.tsx b/src/components/audio/AudioUploader.tsx index fa07f36..f402f97 100644 --- a/src/components/audio/AudioUploader.tsx +++ b/src/components/audio/AudioUploader.tsx @@ -57,6 +57,12 @@ export function AudioUploader({ as="span" disabled={isProcessing} className="cursor-pointer" + trackingId="audio-upload-button" + trackingProperties={{ + category: 'audio', + action: 'upload_click', + label: 'audio_uploader' + }} > {isProcessing ? 'Processando...' : 'Enviar Áudio'} diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx index 0d5424b..80d16cd 100644 --- a/src/components/auth/LoginForm.tsx +++ b/src/components/auth/LoginForm.tsx @@ -6,6 +6,7 @@ import { supabase } from '../../lib/supabase'; import { useDataLayer } from '../../hooks/useDataLayer'; import { useFormTracking } from '../../hooks/useFormTracking'; import { Button } from '../ui/button'; +import { useErrorTracking } from '../../hooks/useErrorTracking'; interface LoginFormProps { userType: 'school' | 'teacher' | 'student'; @@ -39,6 +40,10 @@ export function LoginForm({ userType, onLogin, onRegisterClick }: LoginFormProps formName: `${userType}-login`, category: 'auth' }); + const errorTracking = useErrorTracking({ + category: 'auth', + userEmail: email + }); useEffect(() => { formTracking.trackFormStarted(); @@ -60,13 +65,20 @@ export function LoginForm({ userType, onLogin, onRegisterClick }: LoginFormProps console.log('Resposta do Supabase:', { data, error }); if (error) { + errorTracking.trackApiError(error, '/auth/sign-in', 'POST', { email, userType }); formTracking.trackFormError('auth_error', error.message); throw error; } if (!data.user) { + const err = new Error('Usuário não encontrado'); + errorTracking.trackError(err, { + componentName: 'LoginForm', + action: 'login_attempt', + metadata: { userType } + }); formTracking.trackFormError('user_not_found', 'Usuário não encontrado'); - throw new Error('Usuário não encontrado'); + throw err; } const userRole = data.user.user_metadata.role; @@ -75,8 +87,17 @@ export function LoginForm({ userType, onLogin, onRegisterClick }: LoginFormProps console.log('Role atual:', userRole); if (userRole !== userType) { - formTracking.trackFormError('invalid_role', `Este não é um login de ${userTypeLabels[userType]}`); - throw new Error(`Este não é um login de ${userTypeLabels[userType]}`); + const err = new Error(`Este não é um login de ${userTypeLabels[userType]}`); + errorTracking.trackError(err, { + componentName: 'LoginForm', + action: 'role_validation', + metadata: { + expectedRole: userType, + actualRole: userRole + } + }); + formTracking.trackFormError('invalid_role', err.message); + throw err; } formTracking.trackFormSubmitted(true, { @@ -101,16 +122,14 @@ export function LoginForm({ userType, onLogin, onRegisterClick }: LoginFormProps trackEvent('auth', 'login_success', 'form'); } catch (err) { console.error('Erro no login:', err); - if (err instanceof Error) { - setError(err.message); - } else { - setError('Email ou senha incorretos'); - } + const errorMessage = err instanceof Error ? err.message : 'Email ou senha incorretos'; + setError(errorMessage); + formTracking.trackFormSubmitted(false, { error_type: err instanceof Error ? 'validation_error' : 'unknown_error', - error_message: err instanceof Error ? err.message : 'Email ou senha incorretos' + error_message: errorMessage }); - trackEvent('auth', 'login_error', err.message); + trackEvent('auth', 'login_error', errorMessage); } finally { setLoading(false); } diff --git a/src/hooks/useErrorTracking.ts b/src/hooks/useErrorTracking.ts new file mode 100644 index 0000000..e0fbbaf --- /dev/null +++ b/src/hooks/useErrorTracking.ts @@ -0,0 +1,114 @@ +import * as Sentry from '@sentry/react'; +import { useRudderstack } from './useRudderstack'; + +interface ErrorTrackingOptions { + category?: string; + userId?: string; + userEmail?: string; +} + +export function useErrorTracking(options: ErrorTrackingOptions = {}) { + const { track } = useRudderstack(); + const { category = 'error', userId, userEmail } = options; + + const trackError = ( + error: Error, + context?: { + componentName?: string; + action?: string; + metadata?: Record; + } + ) => { + // 1. Rastreia no Sentry primeiro (para debug técnico) + Sentry.withScope((scope) => { + // Adiciona contexto do usuário + if (userId) scope.setUser({ id: userId, email: userEmail }); + + // Adiciona contexto do erro + if (context?.componentName) scope.setTag('component', context.componentName); + if (context?.action) scope.setTag('action', context.action); + if (context?.metadata) scope.setContext('metadata', context.metadata); + + // Captura o erro + Sentry.captureException(error); + }); + + // 2. Rastreia no Rudderstack (para analytics) + track('error_occurred', { + category, + error_name: error.name, + error_message: error.message, + error_stack: error.stack, + component: context?.componentName, + action: context?.action, + ...context?.metadata, + // Informações do ambiente + url: window.location.href, + user_agent: navigator.userAgent, + timestamp: new Date().toISOString(), + }); + }; + + const trackErrorBoundary = ( + error: Error, + componentStack: string, + componentName: string + ) => { + // 1. Rastreia no Sentry + Sentry.withScope((scope) => { + if (userId) scope.setUser({ id: userId, email: userEmail }); + scope.setTag('component', componentName); + scope.setTag('error_type', 'react_error_boundary'); + scope.setExtra('componentStack', componentStack); + Sentry.captureException(error); + }); + + // 2. Rastreia no Rudderstack + track('error_boundary_triggered', { + category, + error_name: error.name, + error_message: error.message, + component_name: componentName, + component_stack: componentStack, + url: window.location.href, + user_agent: navigator.userAgent, + timestamp: new Date().toISOString(), + }); + }; + + const trackApiError = ( + error: any, + endpoint: string, + method: string, + requestData?: any + ) => { + // 1. Rastreia no Sentry + Sentry.withScope((scope) => { + if (userId) scope.setUser({ id: userId, email: userEmail }); + scope.setTag('error_type', 'api_error'); + scope.setTag('endpoint', endpoint); + scope.setTag('method', method); + scope.setContext('request', { data: requestData }); + Sentry.captureException(error); + }); + + // 2. Rastreia no Rudderstack + track('api_error_occurred', { + category, + endpoint, + method, + error_name: error.name, + error_message: error.message, + status_code: error.status || error.statusCode, + request_data: requestData, + url: window.location.href, + timestamp: new Date().toISOString(), + }); + }; + + return { + trackError, + trackErrorBoundary, + trackApiError, + }; +} \ No newline at end of file