Merge pull request #73 from davidmonterocrespo24/copilot/add-google-analytics-key-events-tracking
Copilot/add google analytics key events trackingpull/74/head
commit
91e9c2d5f6
|
|
@ -11,7 +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 { trackCompileCode, trackRunSimulation, trackStopSimulation, trackResetSimulation, trackOpenLibraryManager } from '../../utils/analytics';
|
||||
import './EditorToolbar.css';
|
||||
|
||||
interface EditorToolbarProps {
|
||||
|
|
@ -160,6 +160,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
|||
const board = boards.find((b) => b.id === activeBoardId);
|
||||
const isQemuBoard = board?.boardKind === 'raspberry-pi-3' || board?.boardKind === 'esp32' || board?.boardKind === 'esp32-s3';
|
||||
if (isQemuBoard || board?.compiledProgram) {
|
||||
trackRunSimulation(board?.boardKind);
|
||||
startBoard(activeBoardId);
|
||||
setMessage(null);
|
||||
return;
|
||||
|
|
@ -176,12 +177,14 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
|||
};
|
||||
|
||||
const handleStop = () => {
|
||||
trackStopSimulation();
|
||||
if (activeBoardId) stopBoard(activeBoardId);
|
||||
else stopSimulation();
|
||||
setMessage(null);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
trackResetSimulation();
|
||||
if (activeBoardId) resetBoard(activeBoardId);
|
||||
else resetSimulation();
|
||||
setMessage(null);
|
||||
|
|
@ -431,7 +434,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
|||
|
||||
{/* Library Manager — always visible with label */}
|
||||
<button
|
||||
onClick={() => setLibManagerOpen(true)}
|
||||
onClick={() => { trackOpenLibraryManager(); setLibManagerOpen(true); }}
|
||||
className="tb-btn-libraries"
|
||||
title="Search and install Arduino libraries"
|
||||
>
|
||||
|
|
@ -516,7 +519,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
|||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
<span>Missing library? Install it from the</span>
|
||||
<button className="tb-lib-hint-btn" onClick={() => { setLibManagerOpen(true); setMissingLibHint(false); }}>
|
||||
<button className="tb-lib-hint-btn" onClick={() => { trackOpenLibraryManager(); setLibManagerOpen(true); setMissingLibHint(false); }}>
|
||||
Library Manager
|
||||
</button>
|
||||
<button className="tb-lib-hint-close" onClick={() => setMissingLibHint(false)} title="Dismiss">
|
||||
|
|
|
|||
|
|
@ -1,7 +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';
|
||||
import { trackVisitGitHub, trackVisitDiscord } from '../../utils/analytics';
|
||||
|
||||
const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio';
|
||||
const DISCORD_URL = 'https://discord.gg/rCScB9cG';
|
||||
|
|
@ -64,13 +64,13 @@ export const AppHeader: React.FC<AppHeaderProps> = () => {
|
|||
<Link to="/docs" className={'header-nav-link' + isActive('/docs')}>Documentation</Link>
|
||||
<Link to="/examples" className={'header-nav-link' + isActive('/examples')}>Examples</Link>
|
||||
<Link to="/editor" className={'header-nav-link' + isActive('/editor')}>Editor</Link>
|
||||
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" className="header-nav-link">
|
||||
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" className="header-nav-link" onClick={trackVisitGitHub}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style={{ flexShrink: 0 }}>
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.021c0 4.428 2.865 8.185 6.839 9.504.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.605-3.369-1.342-3.369-1.342-.454-1.154-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.026 2.747-1.026.546 1.378.202 2.397.1 2.65.64.7 1.028 1.595 1.028 2.688 0 3.848-2.338 4.695-4.566 4.944.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.203 22 16.447 22 12.021 22 6.484 17.523 2 12 2z" />
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
<a href={DISCORD_URL} target="_blank" rel="noopener noreferrer" className="header-nav-link header-nav-discord">
|
||||
<a href={DISCORD_URL} target="_blank" rel="noopener noreferrer" className="header-nav-link header-nav-discord" onClick={trackVisitDiscord}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style={{ flexShrink: 0 }}>
|
||||
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057c.002.022.015.043.032.053a19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -4,7 +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';
|
||||
import { trackCreateProject, trackSaveProject } from '../../utils/analytics';
|
||||
|
||||
interface SaveProjectModalProps {
|
||||
onClose: () => void;
|
||||
|
|
@ -55,6 +55,7 @@ export const SaveProjectModal: React.FC<SaveProjectModalProps> = ({ onClose }) =
|
|||
let saved;
|
||||
if (isUpdate && currentProject) {
|
||||
saved = await updateProject(currentProject.id, payload);
|
||||
trackSaveProject();
|
||||
} else {
|
||||
saved = await createProject(payload);
|
||||
trackCreateProject();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { searchLibraries, installLibrary, getInstalledLibraries } from '../../services/libraryService';
|
||||
import type { ArduinoLibrary, InstalledLibrary } from '../../services/libraryService';
|
||||
import { trackInstallLibrary } from '../../utils/analytics';
|
||||
import './LibraryManagerModal.css';
|
||||
|
||||
interface LibraryManagerModalProps {
|
||||
|
|
@ -79,6 +80,7 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
|
|||
try {
|
||||
const result = await installLibrary(libName);
|
||||
if (result.success) {
|
||||
trackInstallLibrary(libName);
|
||||
setStatusMsg({ type: 'success', text: `"${libName}" installed successfully!` });
|
||||
fetchInstalled(); // Refresh installed list so search tab reflects new state
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import type { ComponentMetadata } from '../../types/component-metadata';
|
|||
import type { BoardKind } from '../../types/board';
|
||||
import { BOARD_KIND_LABELS } from '../../types/board';
|
||||
import { useOscilloscopeStore } from '../../store/useOscilloscopeStore';
|
||||
import { trackSelectBoard, trackAddComponent, trackCreateWire, trackToggleSerialMonitor } from '../../utils/analytics';
|
||||
import './SimulatorCanvas.css';
|
||||
|
||||
export const SimulatorCanvas = () => {
|
||||
|
|
@ -747,6 +748,7 @@ export const SimulatorCanvas = () => {
|
|||
const y = 100 + (row * gridSize);
|
||||
|
||||
const component = createComponentFromMetadata(metadata, x, y);
|
||||
trackAddComponent(metadata.id);
|
||||
addComponent(component as any);
|
||||
setShowComponentPicker(false);
|
||||
};
|
||||
|
|
@ -1001,6 +1003,7 @@ export const SimulatorCanvas = () => {
|
|||
if (wireInProgress) {
|
||||
// Finish wire: connect to this pin
|
||||
finishWireCreation({ componentId, pinName, x, y });
|
||||
trackCreateWire();
|
||||
} else {
|
||||
// Start wire: auto-detect color from pin name
|
||||
startWireCreation({ componentId, pinName, x, y }, autoWireColor(pinName));
|
||||
|
|
@ -1183,7 +1186,7 @@ export const SimulatorCanvas = () => {
|
|||
|
||||
{/* Serial Monitor toggle */}
|
||||
<button
|
||||
onClick={toggleSerialMonitor}
|
||||
onClick={() => { toggleSerialMonitor(); trackToggleSerialMonitor(!serialMonitorOpen); }}
|
||||
className={`canvas-serial-btn${serialMonitorOpen ? ' canvas-serial-btn-active' : ''}`}
|
||||
title="Toggle Serial Monitor"
|
||||
>
|
||||
|
|
@ -1405,6 +1408,7 @@ export const SimulatorCanvas = () => {
|
|||
onClose={() => setShowComponentPicker(false)}
|
||||
onSelectComponent={handleSelectComponent}
|
||||
onSelectBoard={(kind: BoardKind) => {
|
||||
trackSelectBoard(kind);
|
||||
const sameKind = boards.filter((b) => b.boardKind === kind);
|
||||
const newBoardId = sameKind.length === 0 ? kind : `${kind}-${sameKind.length + 1}`;
|
||||
const x = boardPosition.x + boards.length * 60 + 420;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import './SEOPage.css';
|
||||
|
||||
const META = getSeoMeta('/arduino-emulator')!;
|
||||
|
|
@ -86,7 +87,7 @@ export const ArduinoEmulatorPage: React.FC = () => {
|
|||
executed at 16 MHz — the same silicon behavior as real hardware, without buying hardware.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open Emulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('arduino-emulator', '/editor')}>Open Emulator →</Link>
|
||||
<Link to="/docs/emulator" className="seo-btn-secondary">Emulation Details</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · Built on avr8js & rp2040js · No signup required</p>
|
||||
|
|
@ -171,7 +172,7 @@ export const ArduinoEmulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Start emulating Arduino today</h2>
|
||||
<p>Open the editor and execute your firmware against a real emulated CPU — no hardware purchase, no cloud, no limits.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch Emulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('arduino-emulator', '/editor')}>Launch Emulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/arduino-simulator">Arduino Simulator</Link>
|
||||
<Link to="/atmega328p-simulator">ATmega328P Simulator</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import './SEOPage.css';
|
||||
|
||||
const META = getSeoMeta('/arduino-mega-simulator')!;
|
||||
|
|
@ -86,7 +87,7 @@ export const ArduinoMegaSimulatorPage: React.FC = () => {
|
|||
pins, 16 analog inputs, 4 serial ports, and 6 timers. Free and open-source.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open Mega 2560 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('arduino-mega-simulator', '/editor')}>Open Mega 2560 Simulator →</Link>
|
||||
<Link to="/examples" className="seo-btn-secondary">Browse Examples</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · No signup required · Full ATmega2560 emulation</p>
|
||||
|
|
@ -168,7 +169,7 @@ export const ArduinoMegaSimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Simulate your Arduino Mega project</h2>
|
||||
<p>Select the Mega 2560 board in the editor and start simulating — full ATmega2560 emulation, no hardware purchase needed.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch Mega 2560 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('arduino-mega-simulator', '/editor')}>Launch Mega 2560 Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/arduino-simulator">Arduino Simulator</Link>
|
||||
<Link to="/arduino-emulator">Arduino Emulator</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import './SEOPage.css';
|
||||
|
||||
const META = getSeoMeta('/arduino-simulator')!;
|
||||
|
|
@ -86,7 +87,7 @@ export const ArduinoSimulatorPage: React.FC = () => {
|
|||
electronic components. No install, no cloud, no account required.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open Arduino Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('arduino-simulator', '/editor')}>Open Arduino Simulator →</Link>
|
||||
<Link to="/examples" className="seo-btn-secondary">Browse Examples</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · No signup required · Runs 100% in your browser</p>
|
||||
|
|
@ -167,7 +168,7 @@ export const ArduinoSimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Ready to simulate your Arduino?</h2>
|
||||
<p>Open the editor and start coding in seconds — no setup, no install, no account needed.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch Arduino Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('arduino-simulator', '/editor')}>Launch Arduino Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/examples">Example Projects</Link>
|
||||
<Link to="/arduino-emulator">Arduino Emulator</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import './SEOPage.css';
|
||||
|
||||
const META = getSeoMeta('/atmega328p-simulator')!;
|
||||
|
|
@ -86,7 +87,7 @@ export const AtmegaSimulatorPage: React.FC = () => {
|
|||
16 MHz with full GPIO, timer, ADC, and USART emulation. No hardware, no install.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open ATmega328P Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('atmega-simulator', '/editor')}>Open ATmega328P Simulator →</Link>
|
||||
<Link to="/docs/emulator" className="seo-btn-secondary">Technical Details</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · Genuine AVR8 emulation · Runs 100% in your browser</p>
|
||||
|
|
@ -172,7 +173,7 @@ export const AtmegaSimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Simulate your ATmega328P code now</h2>
|
||||
<p>Open the editor, paste your sketch, and click Simulate — no setup, no hardware purchase required.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch ATmega328P Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('atmega-simulator', '/editor')}>Launch ATmega328P Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/arduino-simulator">Arduino Simulator</Link>
|
||||
<Link to="/arduino-emulator">Arduino Emulator</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import esp32C3SvgUrl from '../../../wokwi-libs/wokwi-boards/boards/esp32-c3-devkitm-1/board.svg?url';
|
||||
import './SEOPage.css';
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ export const Esp32C3SimulatorPage: React.FC = () => {
|
|||
RV32IMC at 160 MHz with 48+ interactive components.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open ESP32-C3 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('esp32-c3-simulator', '/editor')}>Open ESP32-C3 Simulator →</Link>
|
||||
<Link to="/examples" className="seo-btn-secondary">C3 Examples</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · 100% browser-native · No backend required</p>
|
||||
|
|
@ -168,7 +169,7 @@ export const Esp32C3SimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Ready to simulate ESP32-C3?</h2>
|
||||
<p>Open the editor, pick an ESP32-C3 board, and start coding — runs instantly in your browser.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch ESP32-C3 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('esp32-c3-simulator', '/editor')}>Launch ESP32-C3 Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/esp32-simulator">ESP32 Simulator</Link>
|
||||
<Link to="/esp32-s3-simulator">ESP32-S3 Simulator</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import esp32S3SvgUrl from '../../../wokwi-libs/wokwi-boards/boards/esp32-s3-devkitc-1/board.svg?url';
|
||||
import './SEOPage.css';
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ export const Esp32S3SimulatorPage: React.FC = () => {
|
|||
USB OTG, vector extensions, 45 GPIOs. Write, compile, and run in seconds.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open ESP32-S3 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('esp32-s3-simulator', '/editor')}>Open ESP32-S3 Simulator →</Link>
|
||||
<Link to="/examples" className="seo-btn-secondary">Browse Examples</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · QEMU Xtensa LX7 · No account needed</p>
|
||||
|
|
@ -150,7 +151,7 @@ export const Esp32S3SimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Ready to simulate ESP32-S3?</h2>
|
||||
<p>Open the editor, select an ESP32-S3 board, and run your code instantly.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch ESP32-S3 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('esp32-s3-simulator', '/editor')}>Launch ESP32-S3 Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/esp32-simulator">ESP32 Simulator</Link>
|
||||
<Link to="/esp32-c3-simulator">ESP32-C3 Simulator</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import esp32SvgUrl from '../../../wokwi-libs/wokwi-boards/boards/esp32-devkit-v1/board.svg?url';
|
||||
import './SEOPage.css';
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ export const Esp32SimulatorPage: React.FC = () => {
|
|||
48+ interactive components, Serial Monitor, no install required.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open ESP32 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('esp32-simulator', '/editor')}>Open ESP32 Simulator →</Link>
|
||||
<Link to="/examples" className="seo-btn-secondary">ESP32 Examples</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · No signup · QEMU-powered emulation</p>
|
||||
|
|
@ -183,7 +184,7 @@ export const Esp32SimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Ready to simulate your ESP32?</h2>
|
||||
<p>Open the editor, select an ESP32 board, and start coding — no setup, no install, no account needed.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch ESP32 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('esp32-simulator', '/editor')}>Launch ESP32 Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/examples">Example Projects</Link>
|
||||
<Link to="/docs/esp32-emulation">ESP32 Docs</Link>
|
||||
|
|
|
|||
|
|
@ -38,9 +38,6 @@ 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] });
|
||||
|
|
@ -54,6 +51,7 @@ export const ExamplesPage: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleLoadExample = async (example: ExampleProject) => {
|
||||
trackOpenExample(example.title);
|
||||
// Auto-install required libraries before loading
|
||||
if (example.libraries && example.libraries.length > 0) {
|
||||
await ensureLibraries(example.libraries);
|
||||
|
|
|
|||
|
|
@ -1,7 +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 { trackVisitGitHub, trackClickCTA } from '../utils/analytics';
|
||||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
|
|
@ -496,7 +496,7 @@ export const LandingPage: React.FC = () => {
|
|||
Raspberry Pi Pico, Raspberry Pi 3, and more. No hardware, no cloud, no limits.
|
||||
</p>
|
||||
<div className="hero-ctas">
|
||||
<Link to="/editor" className="cta-primary">
|
||||
<Link to="/editor" className="cta-primary" onClick={() => trackClickCTA('landing', '/editor')}>
|
||||
<IcoZap />
|
||||
Try Simulator Free →
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
|||
import { login, initiateGoogleLogin } from '../services/authService';
|
||||
import { useAuthStore } from '../store/useAuthStore';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { trackLogin } from '../utils/analytics';
|
||||
|
||||
export const LoginPage: React.FC = () => {
|
||||
useSEO({
|
||||
|
|
@ -25,6 +26,7 @@ export const LoginPage: React.FC = () => {
|
|||
setLoading(true);
|
||||
try {
|
||||
const user = await login(email, password);
|
||||
trackLogin('email');
|
||||
setUser(user);
|
||||
navigate(searchParams.get('redirect') || '/');
|
||||
} catch (err: any) {
|
||||
|
|
@ -71,7 +73,7 @@ export const LoginPage: React.FC = () => {
|
|||
|
||||
<div className="ap-divider">or</div>
|
||||
|
||||
<button onClick={initiateGoogleLogin} className="ap-btn-white">
|
||||
<button onClick={() => { trackLogin('google'); initiateGoogleLogin(); }} className="ap-btn-white">
|
||||
<svg width="18" height="18" viewBox="0 0 48 48">
|
||||
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>
|
||||
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import piPicoSvgUrl from '../../../wokwi-libs/wokwi-boards/boards/pi-pico/board.svg?url';
|
||||
import './SEOPage.css';
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ export const RaspberryPiPicoSimulatorPage: React.FC = () => {
|
|||
at 133 MHz. 26 GPIO pins, I2C, SPI, UART, ADC. No hardware needed.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open Pico Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('rpi-pico-simulator', '/editor')}>Open Pico Simulator →</Link>
|
||||
<Link to="/examples" className="seo-btn-secondary">Pico Examples</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · rp2040js emulation · No account needed</p>
|
||||
|
|
@ -163,7 +164,7 @@ export const RaspberryPiPicoSimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Ready to simulate Raspberry Pi Pico?</h2>
|
||||
<p>Open the editor, select a Pico board, and start coding — no Raspberry Pi hardware required.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch Pico Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('rpi-pico-simulator', '/editor')}>Launch Pico Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/examples">Example Projects</Link>
|
||||
<Link to="/docs/rp2040-emulation">RP2040 Docs</Link>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import raspberryPi3Svg from '../assets/Raspberry_Pi_3_illustration.svg';
|
||||
import './SEOPage.css';
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ export const RaspberryPiSimulatorPage: React.FC = () => {
|
|||
Write Python, control GPIO, install packages. No hardware needed.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">Open Pi 3 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('rpi-simulator', '/editor')}>Open Pi 3 Simulator →</Link>
|
||||
<Link to="/docs/raspberry-pi3-emulation" className="seo-btn-secondary">Read the Docs</Link>
|
||||
</div>
|
||||
<p className="seo-trust">Free & open-source · QEMU ARM64 · Full Raspberry Pi OS</p>
|
||||
|
|
@ -155,7 +156,7 @@ export const RaspberryPiSimulatorPage: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Ready to simulate Raspberry Pi 3?</h2>
|
||||
<p>Open the editor, select Raspberry Pi 3, and boot into Linux — right in your browser.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch Pi 3 Simulator →</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('rpi-simulator', '/editor')}>Launch Pi 3 Simulator →</Link>
|
||||
<div className="seo-internal-links">
|
||||
<Link to="/raspberry-pi-pico-simulator">Pico Simulator</Link>
|
||||
<Link to="/esp32-simulator">ESP32 Simulator</Link>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { register, initiateGoogleLogin } from '../services/authService';
|
|||
import { useAuthStore } from '../store/useAuthStore';
|
||||
import { RESERVED_USERNAMES } from '../utils/reservedUsernames';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { trackSignUp } from '../utils/analytics';
|
||||
|
||||
export const RegisterPage: React.FC = () => {
|
||||
useSEO({
|
||||
|
|
@ -37,6 +38,7 @@ export const RegisterPage: React.FC = () => {
|
|||
setLoading(true);
|
||||
try {
|
||||
const user = await register(username.toLowerCase(), email, password);
|
||||
trackSignUp('email');
|
||||
setUser(user);
|
||||
navigate('/editor');
|
||||
} catch (err: any) {
|
||||
|
|
@ -95,7 +97,7 @@ export const RegisterPage: React.FC = () => {
|
|||
|
||||
<div className="ap-divider">or</div>
|
||||
|
||||
<button onClick={initiateGoogleLogin} className="ap-btn-white">
|
||||
<button onClick={() => { trackSignUp('google'); initiateGoogleLogin(); }} className="ap-btn-white">
|
||||
<svg width="18" height="18" viewBox="0 0 48 48">
|
||||
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>
|
||||
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
|
|||
import { AppHeader } from '../components/layout/AppHeader';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { getSeoMeta } from '../seoRoutes';
|
||||
import { trackClickCTA } from '../utils/analytics';
|
||||
import raspberryPi3Svg from '../assets/Raspberry_Pi_3_illustration.svg';
|
||||
import './SEOPage.css';
|
||||
import './Velxio2Page.css';
|
||||
|
|
@ -265,7 +266,7 @@ export const Velxio2Page: React.FC = () => {
|
|||
Free, open-source, no install needed.
|
||||
</p>
|
||||
<div className="seo-cta-group">
|
||||
<Link to="/editor" className="seo-btn-primary">
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('velxio-v2', '/editor')}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" /></svg>
|
||||
Try Velxio 2.0
|
||||
</Link>
|
||||
|
|
@ -603,7 +604,7 @@ export const Velxio2Page: React.FC = () => {
|
|||
<div className="seo-bottom">
|
||||
<h2>Try Velxio 2.0 now</h2>
|
||||
<p>Open the editor and start simulating — 19 boards, 68+ examples, zero setup.</p>
|
||||
<Link to="/editor" className="seo-btn-primary">Launch Simulator</Link>
|
||||
<Link to="/editor" className="seo-btn-primary" onClick={() => trackClickCTA('velxio-v2', '/editor')}>Launch Simulator</Link>
|
||||
|
||||
<div className="v2-bottom-community">
|
||||
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" className="v2-community-btn v2-star-btn">
|
||||
|
|
|
|||
|
|
@ -13,11 +13,55 @@ function fireEvent(eventName: string, params: Record<string, string | number | b
|
|||
}
|
||||
}
|
||||
|
||||
// ── Simulation ──────────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired when the user starts a simulation (clicks Run). */
|
||||
export function trackRunSimulation(): void {
|
||||
fireEvent('run_simulation', { event_category: 'engagement' });
|
||||
export function trackRunSimulation(board?: string): void {
|
||||
fireEvent('run_simulation', {
|
||||
event_category: 'engagement',
|
||||
...(board ? { board } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Fired when the user stops a running simulation. */
|
||||
export function trackStopSimulation(): void {
|
||||
fireEvent('stop_simulation', { event_category: 'engagement' });
|
||||
}
|
||||
|
||||
/** Fired when the user resets a simulation. */
|
||||
export function trackResetSimulation(): void {
|
||||
fireEvent('reset_simulation', { event_category: 'engagement' });
|
||||
}
|
||||
|
||||
// ── Editor / Canvas ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired when code compilation starts. */
|
||||
export function trackCompileCode(): void {
|
||||
fireEvent('compile_code', { event_category: 'development' });
|
||||
}
|
||||
|
||||
/** Fired when the user changes the board type. */
|
||||
export function trackSelectBoard(board: string): void {
|
||||
fireEvent('select_board', { event_category: 'engagement', board });
|
||||
}
|
||||
|
||||
/** Fired when the user adds a component to the canvas. */
|
||||
export function trackAddComponent(componentType: string): void {
|
||||
fireEvent('add_component', { event_category: 'engagement', component_type: componentType });
|
||||
}
|
||||
|
||||
/** Fired when the user finishes creating a wire between two pins. */
|
||||
export function trackCreateWire(): void {
|
||||
fireEvent('create_wire', { event_category: 'engagement' });
|
||||
}
|
||||
|
||||
/** Fired when the user opens or closes the Serial Monitor. */
|
||||
export function trackToggleSerialMonitor(open: boolean): void {
|
||||
fireEvent('toggle_serial_monitor', { event_category: 'engagement', state: open ? 'open' : 'close' });
|
||||
}
|
||||
|
||||
// ── Examples ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired when a user loads a sample project from the examples gallery. */
|
||||
export function trackOpenExample(exampleTitle?: string): void {
|
||||
fireEvent('open_example', {
|
||||
|
|
@ -26,17 +70,57 @@ export function trackOpenExample(exampleTitle?: string): void {
|
|||
});
|
||||
}
|
||||
|
||||
// ── Projects ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** 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 saves/updates an existing project. */
|
||||
export function trackSaveProject(): void {
|
||||
fireEvent('save_project', { event_category: 'engagement' });
|
||||
}
|
||||
|
||||
// ── Auth ────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired on successful sign-up. */
|
||||
export function trackSignUp(method: 'email' | 'google'): void {
|
||||
fireEvent('sign_up', { event_category: 'auth', method });
|
||||
}
|
||||
|
||||
/** Fired on successful login. */
|
||||
export function trackLogin(method: 'email' | 'google'): void {
|
||||
fireEvent('login', { event_category: 'auth', method });
|
||||
}
|
||||
|
||||
// ── External Links ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired when a user clicks any GitHub repository link. */
|
||||
export function trackVisitGitHub(): void {
|
||||
fireEvent('visit_github', { event_category: 'external_link' });
|
||||
}
|
||||
|
||||
/** Fired when a user clicks any Discord invite link. */
|
||||
export function trackVisitDiscord(): void {
|
||||
fireEvent('visit_discord', { event_category: 'external_link' });
|
||||
}
|
||||
|
||||
// ── CTA / Conversion ────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired when a user clicks a CTA button on a landing/SEO page. */
|
||||
export function trackClickCTA(source: string, destination: string): void {
|
||||
fireEvent('click_cta', { event_category: 'conversion', source, destination });
|
||||
}
|
||||
|
||||
// ── Library Manager ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Fired when a user opens the Library Manager panel. */
|
||||
export function trackOpenLibraryManager(): void {
|
||||
fireEvent('open_library_manager', { event_category: 'engagement' });
|
||||
}
|
||||
|
||||
/** Fired when a user installs a library. */
|
||||
export function trackInstallLibrary(libraryName: string): void {
|
||||
fireEvent('install_library', { event_category: 'development', library_name: libraryName });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue