fix: PageTracker geral

This commit is contained in:
Lucas Santana 2025-01-17 12:39:10 -03:00
parent 98411b2aa1
commit bcbdd07a41
12 changed files with 321 additions and 223 deletions

View File

@ -12,6 +12,8 @@ import { AuthProvider } from './contexts/AuthContext'
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Toaster } from './components/ui/toaster'; import { Toaster } from './components/ui/toaster';
import { router } from './routes';
import { RouterProvider } from 'react-router-dom';
type AppStep = type AppStep =
| 'welcome' | 'welcome'
@ -88,6 +90,7 @@ export function App() {
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AuthProvider> <AuthProvider>
<RouterProvider router={router} />
<div className="min-h-screen bg-gradient-to-b from-purple-100 to-blue-100"> <div className="min-h-screen bg-gradient-to-b from-purple-100 to-blue-100">
{step === 'welcome' && ( {step === 'welcome' && (
<WelcomePage <WelcomePage

View File

@ -70,7 +70,6 @@ export function PageTracker() {
...userTraits, ...userTraits,
// Metadados adicionais // Metadados adicionais
timestamp: new Date().toISOString(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
}); });

View File

@ -2,6 +2,71 @@
Este diretório contém a implementação do sistema de analytics do Leiturama, utilizando Rudderstack como principal ferramenta de tracking. Este diretório contém a implementação do sistema de analytics do Leiturama, utilizando Rudderstack como principal ferramenta de tracking.
## 🚀 Inicialização
Para inicializar o sistema de analytics corretamente, certifique-se de:
1. Configurar as variáveis de ambiente:
```env
VITE_RUDDERSTACK_WRITE_KEY=seu_write_key
VITE_RUDDERSTACK_DATA_PLANE_URL=sua_url
```
2. Inicializar o analytics antes de usar:
```typescript
// main.tsx
import { analytics } from './lib/analytics';
// Inicializa o analytics antes de renderizar o app
await analytics.init();
// Renderiza o app
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
```
## 🔧 Troubleshooting
### Erros Comuns
1. **Falha na Inicialização**
```typescript
Failed to initialize analytics: Event {...}
```
Possíveis causas:
- Variáveis de ambiente não configuradas
- Script do Rudderstack bloqueado
- Erro na carga do script
Soluções:
- Verifique as variáveis de ambiente
- Verifique se o domínio do Rudderstack está liberado
- Adicione tratamento de erro na inicialização:
```typescript
try {
await analytics.init();
} catch (error) {
console.error('Falha ao inicializar analytics:', error);
// Continue renderizando o app mesmo com falha no analytics
}
```
2. **Eventos Não Rastreados**
Se os eventos não estão sendo rastreados, verifique:
- Se o analytics foi inicializado corretamente
- Se há erros no console
- Se o writeKey e dataPlaneUrl estão corretos
- Se há bloqueadores de rastreamento no navegador
3. **Erros de Tipo**
Se encontrar erros de tipo ao usar os hooks:
- Verifique se está usando as interfaces corretas
- Importe os tipos necessários
- Use as constantes de EVENT_CATEGORIES
## 📦 Componentes ## 📦 Componentes
### PageTracker ### PageTracker

View File

@ -0,0 +1,11 @@
import { Outlet } from 'react-router-dom';
import { PageTracker } from '../analytics/PageTracker';
export function BaseLayout() {
return (
<>
<PageTracker />
<Outlet />
</>
);
}

View File

@ -134,7 +134,7 @@ export const EVENT_PROPERTIES = {
STARTED_AT: 'started_at', STARTED_AT: 'started_at',
COMPLETED_AT: 'completed_at', COMPLETED_AT: 'completed_at',
DURATION: 'duration', DURATION: 'duration',
TIMESTAMP: 'timestamp', EVENT_TIMESTAMP: 'event_timestamp',
}, },
// Propriedades de Formulário // Propriedades de Formulário

View File

@ -36,7 +36,6 @@ export function useButtonTracking(options: ButtonTrackingOptions = {}) {
button_id: buttonId, button_id: buttonId,
category, category,
location, location,
timestamp: new Date().toISOString(),
...properties, ...properties,
// Informações da página // Informações da página
page_title: document.title, page_title: document.title,

View File

@ -44,7 +44,6 @@ export function useErrorTracking(options: ErrorTrackingOptions = {}) {
// Informações do ambiente // Informações do ambiente
url: window.location.href, url: window.location.href,
user_agent: navigator.userAgent, user_agent: navigator.userAgent,
timestamp: new Date().toISOString(),
}); });
}; };
@ -71,7 +70,6 @@ export function useErrorTracking(options: ErrorTrackingOptions = {}) {
component_stack: componentStack, component_stack: componentStack,
url: window.location.href, url: window.location.href,
user_agent: navigator.userAgent, user_agent: navigator.userAgent,
timestamp: new Date().toISOString(),
}); });
}; };
@ -101,7 +99,6 @@ export function useErrorTracking(options: ErrorTrackingOptions = {}) {
status_code: error.status || error.statusCode, status_code: error.status || error.statusCode,
request_data: requestData, request_data: requestData,
url: window.location.href, url: window.location.href,
timestamp: new Date().toISOString(),
}); });
}; };

View File

@ -50,35 +50,30 @@ export function useStudentTracking() {
const trackStoryGenerated = (properties: StoryGeneratedProps) => { const trackStoryGenerated = (properties: StoryGeneratedProps) => {
analytics.track('story_generated', { analytics.track('story_generated', {
...properties, ...properties,
timestamp: new Date().toISOString()
}); });
}; };
const trackAudioRecorded = (properties: AudioRecordedProps) => { const trackAudioRecorded = (properties: AudioRecordedProps) => {
analytics.track('audio_recorded', { analytics.track('audio_recorded', {
...properties, ...properties,
timestamp: new Date().toISOString()
}); });
}; };
const trackExerciseCompleted = (properties: ExerciseCompletedProps) => { const trackExerciseCompleted = (properties: ExerciseCompletedProps) => {
analytics.track('exercise_completed', { analytics.track('exercise_completed', {
...properties, ...properties,
timestamp: new Date().toISOString()
}); });
}; };
const trackInterestAdded = (properties: InterestActionProps) => { const trackInterestAdded = (properties: InterestActionProps) => {
analytics.track('interest_added', { analytics.track('interest_added', {
...properties, ...properties,
timestamp: new Date().toISOString()
}); });
}; };
const trackInterestRemoved = (properties: InterestActionProps) => { const trackInterestRemoved = (properties: InterestActionProps) => {
analytics.track('interest_removed', { analytics.track('interest_removed', {
...properties, ...properties,
timestamp: new Date().toISOString()
}); });
}; };

View File

@ -47,58 +47,68 @@ export class Analytics {
} }
// Inicializa o Rudderstack // Inicializa o Rudderstack
init(options?: { writeKey?: string; dataPlaneUrl?: string }): Promise<void> { async init(): Promise<void> {
if (this.initPromise) return this.initPromise; if (this.initPromise) return this.initPromise;
this.initPromise = new Promise((resolve, reject) => { this.initPromise = new Promise<void>((resolve, reject) => {
const writeKey = options?.writeKey || this.writeKey || import.meta.env.VITE_RUDDERSTACK_WRITE_KEY; const initializeAnalytics = async () => {
const dataPlaneUrl = options?.dataPlaneUrl || this.dataPlaneUrl || import.meta.env.VITE_RUDDERSTACK_DATA_PLANE_URL; try {
const writeKey = this.writeKey;
const dataPlaneUrl = this.dataPlaneUrl;
if (!writeKey || !dataPlaneUrl) { if (this.debug) {
const error = new Error('Missing Rudderstack configuration'); console.log('Inicializando analytics com:', {
this.handleError('init', error); writeKey,
reject(error); dataPlaneUrl,
return; debug: this.debug
});
} }
try { if (!writeKey || !dataPlaneUrl) {
throw new Error('Analytics configuration missing');
}
await new Promise<void>((scriptResolve, scriptReject) => {
const script = document.createElement('script'); const script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js'; script.src = 'https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js';
script.crossOrigin = 'anonymous';
script.async = true; script.async = true;
script.onload = () => { script.onload = () => {
if (window.rudderanalytics) { if (!window.rudderanalytics) {
scriptReject(new Error('Rudderstack failed to initialize'));
return;
}
window.rudderanalytics.load(writeKey, dataPlaneUrl, { window.rudderanalytics.load(writeKey, dataPlaneUrl, {
configUrl: 'https://api.rudderlabs.com', configUrl: 'https://api.rudderlabs.com',
logLevel: this.debug ? 'DEBUG' : 'ERROR', logLevel: this.debug ? 'DEBUG' : 'ERROR',
secureCookie: true,
crossDomainLinker: true
}); });
scriptResolve();
// Espera um pequeno intervalo para garantir que o Rudderstack está pronto
setTimeout(() => {
this.initialized = true;
this.processEventQueue();
if (this.debug) {
console.log('Rudderstack initialized successfully');
}
resolve();
}, 100);
} else {
const error = new Error('Failed to load Rudderstack');
this.handleError('init', error);
reject(error);
}
}; };
script.onerror = (error) => { script.onerror = (error) => {
this.handleError('init', error); scriptReject(new Error('Failed to load Rudderstack script: ' + error));
reject(error);
}; };
document.head.appendChild(script); document.head.appendChild(script);
});
await new Promise((waitResolve) => setTimeout(waitResolve, 1000));
this.initialized = true;
this.processEventQueue();
resolve();
} catch (error) { } catch (error) {
console.error('Analytics initialization failed:', error);
this.handleError('init', error); this.handleError('init', error);
reject(error); reject(error);
} }
};
initializeAnalytics();
}); });
return this.initPromise; return this.initPromise;
@ -210,7 +220,7 @@ export class Analytics {
// Enriquece as propriedades com dados padrão // Enriquece as propriedades com dados padrão
private enrichEventProperties(properties?: Record<string, unknown>): BaseEventProperties { private enrichEventProperties(properties?: Record<string, unknown>): BaseEventProperties {
const baseProperties: BaseEventProperties = { const baseProperties: BaseEventProperties = {
[EVENT_PROPERTIES.TIMING.TIMESTAMP]: new Date().toISOString(), [EVENT_PROPERTIES.TIMING.EVENT_TIMESTAMP]: new Date().toISOString(),
[EVENT_PROPERTIES.PAGE.URL]: window.location.href, [EVENT_PROPERTIES.PAGE.URL]: window.location.href,
[EVENT_PROPERTIES.PAGE.TITLE]: document.title, [EVENT_PROPERTIES.PAGE.TITLE]: document.title,
[EVENT_PROPERTIES.PAGE.PATH]: window.location.pathname, [EVENT_PROPERTIES.PAGE.PATH]: window.location.pathname,
@ -248,5 +258,7 @@ export class Analytics {
// Instância global // Instância global
export const analytics = new Analytics({ export const analytics = new Analytics({
debug: import.meta.env.DEV debug: import.meta.env.DEV,
writeKey: import.meta.env.VITE_RUDDERSTACK_WRITE_KEY,
dataPlaneUrl: import.meta.env.VITE_RUDDERSTACK_DATA_PLANE_URL
}); });

View File

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { AudioRecorderDemo } from '../../components/demo/AudioRecorderDemo'; import { AudioRecorderDemo } from '../../components/demo/AudioRecorderDemo';
import { ArrowRight, Sparkles } from 'lucide-react'; import { ArrowRight, Sparkles } from 'lucide-react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { analytics } from '../../lib/analytics';
export function DemoPage() { export function DemoPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -14,6 +15,15 @@ export function DemoPage() {
const handleDemoComplete = (result: typeof demoResult) => { const handleDemoComplete = (result: typeof demoResult) => {
setDemoResult(result); setDemoResult(result);
// Rastreia quando o resultado é exibido
if (result) {
analytics.track('demo_completed', {
fluency: result.fluency,
accuracy: result.accuracy,
confidence: result.confidence,
has_feedback: !!result.feedback
});
}
}; };
return ( return (

View File

@ -1,4 +1,5 @@
import { createBrowserRouter } from 'react-router-dom'; import { createBrowserRouter } from 'react-router-dom';
import { BaseLayout } from './components/layouts/BaseLayout';
import { HomePage } from './components/home/HomePage'; import { HomePage } from './components/home/HomePage';
import { LoginForm } from './components/auth/LoginForm'; import { LoginForm } from './components/auth/LoginForm';
import { SchoolRegistrationForm } from './components/auth/SchoolRegistrationForm'; import { SchoolRegistrationForm } from './components/auth/SchoolRegistrationForm';
@ -44,6 +45,10 @@ function RootLayout({ children }: { children: React.ReactNode }) {
} }
export const router = createBrowserRouter([ export const router = createBrowserRouter([
{
path: '/',
element: <BaseLayout />,
children: [
{ {
path: '/', path: '/',
element: <RootLayout><HomePage /></RootLayout>, element: <RootLayout><HomePage /></RootLayout>,
@ -223,4 +228,6 @@ export const router = createBrowserRouter([
path: '/aluno/historias/:id/exercicios/:type', path: '/aluno/historias/:id/exercicios/:type',
element: <ExercisePage /> element: <ExercisePage />
} }
]
}
]); ]);