diff --git a/docs/analytics.md b/docs/analytics.md new file mode 100644 index 00000000..8f593e6a --- /dev/null +++ b/docs/analytics.md @@ -0,0 +1,108 @@ +# Analytics Events + +Velxio uses **Google Analytics 4 (GA4)** to measure key user interactions. Events are fired via `gtag` and should be marked as **Key Events** in the GA4 dashboard to track conversions and engagement. + +The tracking utility lives in [`frontend/src/utils/analytics.ts`](../frontend/src/utils/analytics.ts). + +--- + +## Key Events + +### `run_simulation` + +Fired when the user clicks the **Run** button to start a simulation. + +| Property | Value | +|------------------|--------------| +| `event_category` | `engagement` | + +**Location:** `EditorToolbar.tsx` → `handleRun()` + +--- + +### `open_example` + +Fired when a user loads a sample project from the Examples gallery. + +| Property | Value | +|------------------|------------------------| +| `event_category` | `engagement` | +| `event_label` | Title of the example | + +**Location:** `ExamplesPage.tsx` → `handleLoadExample()` + +--- + +### `create_project` + +Fired when a user successfully saves a **new** project (not when updating an existing one). + +| Property | Value | +|------------------|--------------| +| `event_category` | `engagement` | + +**Location:** `SaveProjectModal.tsx` → `handleSave()` + +--- + +### `compile_code` + +Fired when code compilation starts (user clicks the **Compile** button). + +| Property | Value | +|------------------|---------------| +| `event_category` | `development` | + +**Location:** `EditorToolbar.tsx` → `handleCompile()` + +--- + +### `visit_github` + +Fired when a user clicks any link pointing to the Velxio GitHub repository. + +| Property | Value | +|------------------|-----------------| +| `event_category` | `external_link` | + +**Locations:** +- `LandingPage.tsx` — nav bar, hero CTA, and footer GitHub links +- `AppHeader.tsx` — editor header GitHub link + +--- + +## Implementation + +All helpers are exported from `frontend/src/utils/analytics.ts`: + +```typescript +import { + trackRunSimulation, + trackOpenExample, + trackCreateProject, + trackCompileCode, + trackVisitGitHub, +} from '../utils/analytics'; +``` + +Example usage: + +```typescript +// Fire an event +trackRunSimulation(); + +// Fire an event with a label +trackOpenExample('Blink LED'); +``` + +The module safely checks whether `gtag` is available before calling it, so it does not throw errors in environments where the GA script is not loaded (e.g., local development without the GA tag). + +--- + +## Marking Events as Key Events in GA4 + +1. Open **Google Analytics → Admin → Events**. +2. Locate the event by name (e.g. `run_simulation`). +3. Toggle **Mark as key event** to enable conversion tracking. + +Repeat for each event listed above. diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index c94d1744..876d78a3 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -11,6 +11,7 @@ import { InstallLibrariesModal } from '../simulator/InstallLibrariesModal'; import { parseCompileResult } from '../../utils/compilationLogger'; import type { CompilationLog } from '../../utils/compilationLogger'; import { exportToWokwiZip, importFromWokwiZip } from '../../utils/wokwiZip'; +import { trackCompileCode, trackRunSimulation } from '../../utils/analytics'; import './EditorToolbar.css'; interface EditorToolbarProps { @@ -99,6 +100,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi setCompiling(true); setMessage(null); setConsoleOpen(true); + trackCompileCode(); const kind = activeBoard?.boardKind; @@ -165,6 +167,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi } // legacy fallback if (compiledHex) { + trackRunSimulation(); startSimulation(); setMessage(null); } else { diff --git a/frontend/src/components/layout/AppHeader.tsx b/frontend/src/components/layout/AppHeader.tsx index 1af65d4a..594239ae 100644 --- a/frontend/src/components/layout/AppHeader.tsx +++ b/frontend/src/components/layout/AppHeader.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useEffect } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { useAuthStore } from '../../store/useAuthStore'; +import { trackVisitGitHub } from '../../utils/analytics'; const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio'; const DISCORD_URL = 'https://discord.gg/rCScB9cG'; diff --git a/frontend/src/components/layout/SaveProjectModal.tsx b/frontend/src/components/layout/SaveProjectModal.tsx index 443b6ee2..015b4baf 100644 --- a/frontend/src/components/layout/SaveProjectModal.tsx +++ b/frontend/src/components/layout/SaveProjectModal.tsx @@ -4,6 +4,7 @@ import { useEditorStore } from '../../store/useEditorStore'; import { useSimulatorStore } from '../../store/useSimulatorStore'; import { useProjectStore } from '../../store/useProjectStore'; import { createProject, updateProject } from '../../services/projectService'; +import { trackCreateProject } from '../../utils/analytics'; interface SaveProjectModalProps { onClose: () => void; @@ -56,6 +57,7 @@ export const SaveProjectModal: React.FC = ({ onClose }) = saved = await updateProject(currentProject.id, payload); } else { saved = await createProject(payload); + trackCreateProject(); } setCurrentProject({ id: saved.id, diff --git a/frontend/src/pages/ExamplesPage.tsx b/frontend/src/pages/ExamplesPage.tsx index d6765d08..5f67d6d1 100644 --- a/frontend/src/pages/ExamplesPage.tsx +++ b/frontend/src/pages/ExamplesPage.tsx @@ -16,6 +16,7 @@ import { useVfsStore } from '../store/useVfsStore'; import { isBoardComponent } from '../utils/boardPinMapping'; import { getInstalledLibraries, installLibrary } from '../services/libraryService'; import type { ExampleProject } from '../data/examples'; +import { trackOpenExample } from '../utils/analytics'; import type { BoardKind } from '../types/board'; export const ExamplesPage: React.FC = () => { @@ -37,6 +38,9 @@ export const ExamplesPage: React.FC = () => { const missing = libs.filter((l) => !installedNames.has(l.toLowerCase())); if (missing.length === 0) return; + const handleLoadExample = (example: ExampleProject) => { + console.log('Loading example:', example.title); + trackOpenExample(example.title); setInstalling({ total: missing.length, done: 0, current: missing[0] }); for (let i = 0; i < missing.length; i++) { setInstalling({ total: missing.length, done: i, current: missing[i] }); diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index 221a318d..7e631703 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useEffect } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuthStore } from '../store/useAuthStore'; +import { trackVisitGitHub } from '../utils/analytics'; import { AppHeader } from '../components/layout/AppHeader'; import { useSEO } from '../utils/useSEO'; import { getSeoMeta } from '../seoRoutes'; @@ -499,13 +500,13 @@ export const LandingPage: React.FC = () => { Try Simulator Free → - + View on GitHub

No signup required · Runs 100% in your browser · Free & open-source

- +
Velxio simulator preview @@ -734,7 +735,7 @@ export const LandingPage: React.FC = () => { Velxio
- GitHub + GitHub Docs Examples Editor diff --git a/frontend/src/utils/analytics.ts b/frontend/src/utils/analytics.ts new file mode 100644 index 00000000..87dc6d90 --- /dev/null +++ b/frontend/src/utils/analytics.ts @@ -0,0 +1,42 @@ +/** + * Google Analytics 4 Key Events Tracking + * + * Provides helper functions to fire GA4 custom events for key user actions. + * Each function maps to an event that should be marked as a Key Event in GA4. + */ + +declare function gtag(command: 'event', eventName: string, eventParams?: Record): void; + +function fireEvent(eventName: string, params: Record): void { + if (typeof gtag === 'function') { + gtag('event', eventName, params); + } +} + +/** Fired when the user starts a simulation (clicks Run). */ +export function trackRunSimulation(): void { + fireEvent('run_simulation', { event_category: 'engagement' }); +} + +/** Fired when a user loads a sample project from the examples gallery. */ +export function trackOpenExample(exampleTitle?: string): void { + fireEvent('open_example', { + event_category: 'engagement', + ...(exampleTitle ? { event_label: exampleTitle } : {}), + }); +} + +/** Fired when a user successfully creates a new project. */ +export function trackCreateProject(): void { + fireEvent('create_project', { event_category: 'engagement' }); +} + +/** Fired when code compilation starts. */ +export function trackCompileCode(): void { + fireEvent('compile_code', { event_category: 'development' }); +} + +/** Fired when a user clicks any GitHub repository link. */ +export function trackVisitGitHub(): void { + fireEvent('visit_github', { event_category: 'external_link' }); +}