Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ __pycache__/
*.ntvs*
*.njsproj
*.sln
*.sw?
*.sw?
.env
62 changes: 60 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"html2canvas": "^1.4.1",
"jspdf": "^4.2.1",
"lucide-react": "^0.344.0",
"posthog-js": "^1.395.0",
"react": "^18.3.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
Expand Down
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import ReportPage from './pages/ReportPage';
import HistoryPage from './pages/HistoryPage';
import AnalyticsDashboard from './pages/AnalyticsDashboard';
import NotFoundPage from './pages/NotFoundPage';
import { PostHogProvider } from './utils/posthog/PostHogProvider';

function App() {
return (
<Routes>
<PostHogProvider>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Dashboard />} />
<Route path="scan" element={<ScanPage />} />
Expand All @@ -20,6 +22,7 @@ function App() {
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</PostHogProvider>
);
}

Expand Down
5 changes: 4 additions & 1 deletion src/components/ScanUrlForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Search } from 'lucide-react';
import { motion } from 'framer-motion';
import { useNavigate } from 'react-router-dom';
import { useScanner } from '../hooks/useScanner';
import { captureEvent } from '../utils/posthog/helpers';
import { EVENTS } from '../utils/posthog/events';

interface ScanUrlFormProps {
onSubmit?: (url: string) => void;
Expand Down Expand Up @@ -39,7 +41,8 @@ const ScanUrlForm = ({ onSubmit, isScanning: externalIsScanning, disabled }: Sca
try {
new URL(processedUrl);
setError('');

captureEvent(EVENTS.SCAN_SUBMITTED, { url: processedUrl });

if (onSubmit) {
onSubmit(processedUrl);
} else {
Expand Down
13 changes: 11 additions & 2 deletions src/pages/HistoryPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useState, useEffect } from 'react';
import { captureEvent } from '../utils/posthog/helpers';
import { EVENTS } from '../utils/posthog/events';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import {
Expand Down Expand Up @@ -35,7 +37,6 @@ const HistoryPage = () => {
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [selectedScans, setSelectedScans] = useState<string[]>([]);
const [successMessage, setSuccessMessage] = useState<string | null>(null);

// Fetch scan history from backend
const fetchScanHistory = async () => {
try {
Expand Down Expand Up @@ -126,7 +127,14 @@ const HistoryPage = () => {

// Handle search
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
const value = e.target.value;
setSearchTerm(value);
if (value.length > 2) {
captureEvent(EVENTS.HISTORY_SEARCHED, {
search_term: value,
result_count: scanHistory.filter(s => s.url.toLowerCase().includes(value.toLowerCase())).length,
});
}
};

// Toggle sort order
Expand Down Expand Up @@ -162,6 +170,7 @@ const HistoryPage = () => {
const success = await deleteScansFromBackend(selectedScans);

if (success) {
captureEvent(EVENTS.HISTORY_SCANS_DELETED, { deleted_count: selectedScans.length });
// Remove from local state
setScanHistory(prev => prev.filter(scan => !selectedScans.includes(scan.id)));
setSelectedScans([]);
Expand Down
27 changes: 24 additions & 3 deletions src/pages/ReportPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

import React, { useEffect, useState, useRef } from "react";
import { captureEvent, captureException } from "../utils/posthog/helpers";
import { EVENTS } from "../utils/posthog/events";
import { useLocation, useParams, useNavigate } from "react-router-dom";
import {
AiOutlineFileDone,
Expand Down Expand Up @@ -331,6 +333,7 @@ const ReportPage: React.FC = () => {
const copyReportLink = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
captureEvent(EVENTS.REPORT_LINK_COPIED, { scan_id: scanId ?? id });
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
Expand Down Expand Up @@ -380,6 +383,11 @@ const ReportPage: React.FC = () => {
: hookResult.date,
};
setReportData(normalizedResult);
captureEvent(EVENTS.SCAN_COMPLETED, {
scan_id: currentScanId,
url: hookResult.url,
accessibility_score: hookResult.score ?? (hookResult as { results?: { score?: number } }).results?.score,
});
setLoading(false);
scanInProgress.current = false;
return;
Expand Down Expand Up @@ -459,6 +467,12 @@ const ReportPage: React.FC = () => {
throw new Error("No valid scan ID or URL provided for report generation.");
} catch (err: unknown) {
console.error("Scan error caught:", err);
captureException(err, { scan_id: currentScanId, url: urlFromQuery });
captureEvent(EVENTS.SCAN_FAILED, {
scan_id: currentScanId,
url: urlFromQuery,
error_message: err instanceof Error ? err.message : String(err),
});
setReportData(null);

if (currentScanId && (currentScanId.startsWith("http") || currentScanId.includes("."))) {
Expand Down Expand Up @@ -491,9 +505,14 @@ const ReportPage: React.FC = () => {
}
}, [scanId, id, urlFromQuery, fetchReport, navigate, error]);




useEffect(() => {
if (!reportData) return;
captureEvent(EVENTS.REPORT_VIEWED, {
scan_id: reportData._id ?? reportData.id ?? scanId ?? id,
url: reportData.url,
accessibility_score: reportData.score,
});
}, [reportData]);

const getScoreBadgeColor = (score: number): string => {
if (score >= 90) return "bg-green-100 text-green-800";
Expand Down Expand Up @@ -581,6 +600,7 @@ const ReportPage: React.FC = () => {
heightLeft -= pageHeight;
}
pdf.save("accessibility-report.pdf");
captureEvent(EVENTS.REPORT_DOWNLOADED, { scan_id: scanId ?? id, format: "pdf" });
};

const downloadJSON = () => {
Expand All @@ -594,6 +614,7 @@ const ReportPage: React.FC = () => {
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
captureEvent(EVENTS.REPORT_DOWNLOADED, { scan_id: scanId ?? id, format: "json" });
};

return (
Expand Down
21 changes: 21 additions & 0 deletions src/utils/posthog/PostHogProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import posthog from 'posthog-js';

posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
capture_pageview: false,
loaded: (ph) => {
if (import.meta.env.DEV) ph.debug();
},
});

export function PostHogProvider({ children }: { children: React.ReactNode }) {
const location = useLocation();

useEffect(() => {
posthog.capture('$pageview');
}, [location]);

return <>{children}</>;
}
11 changes: 11 additions & 0 deletions src/utils/posthog/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const EVENTS = {
SCAN_SUBMITTED: "scan_submitted",
SCAN_COMPLETED: "scan_completed",
SCAN_FAILED: "scan_failed",
REPORT_VIEWED: "report_viewed",
REPORT_DOWNLOADED: "report_downloaded",
REPORT_LINK_COPIED: "report_link_copied",
HISTORY_SCANS_DELETED: "history_scans_deleted",
HISTORY_SEARCHED: "history_searched",
REPORT_ISSUE_FIX_CLICKED: "report_issue_fix_clicked",
} as const;
34 changes: 34 additions & 0 deletions src/utils/posthog/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import posthog from "posthog-js";

/**
* Associates the current session with a user identity in PostHog.
* @param userId - Unique identifier for the user
* @param traits - Optional properties to attach to the user profile
*/
export function identifyUser(userId: string, traits?: Record<string, unknown>) {
posthog.identify(userId, traits);
}

/**
* Tracks a named event with optional metadata.
* @param event - Event name
* @param properties - Optional key-value pairs describing the event
*/
export function captureEvent(
event: string,
properties?: Record<string, unknown>,
) {
posthog.capture(event, properties);
}

/**
* Reports an exception to PostHog error tracking.
* @param error - The caught error or unknown value
* @param properties - Optional additional context
*/
export function captureException(
error: unknown,
properties?: Record<string, unknown>,
) {
posthog.captureException(error, properties);
}
Loading