diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c3426cd..e630389 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -10,6 +10,7 @@ import { Batch } from './pages/Batch';
import { AnalyticsPage } from './pages/Analytics';
import { About } from './pages/About';
import { AdminUsers } from './pages/AdminUsers';
+import { Compare } from './pages/Compare';
const queryClient = new QueryClient({
defaultOptions: {
@@ -41,6 +42,7 @@ function App() {
} />
} />
} />
+ } />
} />
{/* Admin routes */}
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index 501dc1f..0f5afbf 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -1,6 +1,6 @@
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
-import { Search, Layers, BarChart3, Info, Users, LogOut } from 'lucide-react';
+import { Search, Layers, BarChart3, Info, Users, LogOut, GitCompareArrows } from 'lucide-react';
export function Layout() {
const { user, isAdmin, logout } = useAuth();
@@ -15,6 +15,7 @@ export function Layout() {
{ to: '/analysis', icon: Search, label: 'Analysis' },
{ to: '/batch', icon: Layers, label: 'Batch' },
{ to: '/analytics', icon: BarChart3, label: 'Analytics' },
+ { to: '/compare', icon: GitCompareArrows, label: 'Compare' },
{ to: '/about', icon: Info, label: 'About' },
];
diff --git a/frontend/src/pages/Compare.tsx b/frontend/src/pages/Compare.tsx
new file mode 100644
index 0000000..eef3e53
--- /dev/null
+++ b/frontend/src/pages/Compare.tsx
@@ -0,0 +1,161 @@
+import { useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { useQuery } from '@tanstack/react-query';
+import { analysisApi } from '../api/client';
+import { GitCompareArrows, AlertCircle, FileText, Clock } from 'lucide-react';
+import type { CompanyAnalysis } from '../types';
+
+function CompanyPanel({ data, isLoading, isError }: { data?: CompanyAnalysis; isLoading: boolean; isError: boolean }) {
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+
+
+
Failed to load analysis. Check the company name and try again.
+
+
+ );
+ }
+
+ if (!data) return null;
+
+ return (
+
+
+ {data.company_name.toUpperCase()}
+
+
+
+
+
+
{data.patent_count}
+
Patents
+
+
+
+
+ {new Date(data.timestamp).toLocaleDateString()}
+
+
Analyzed
+
+
+
+ {data.success && data.analysis ? (
+
+ {data.analysis}
+
+ ) : (
+
{data.error || 'Analysis not available'}
+ )}
+
+ );
+}
+
+export function Compare() {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [companyA, setCompanyA] = useState(searchParams.get('a') || '');
+ const [companyB, setCompanyB] = useState(searchParams.get('b') || '');
+
+ const queryA = searchParams.get('a') || '';
+ const queryB = searchParams.get('b') || '';
+
+ const resultA = useQuery({
+ queryKey: ['analyze', queryA],
+ queryFn: () => analysisApi.analyzeCompany(queryA),
+ enabled: !!queryA,
+ });
+
+ const resultB = useQuery({
+ queryKey: ['analyze', queryB],
+ queryFn: () => analysisApi.analyzeCompany(queryB),
+ enabled: !!queryB,
+ });
+
+ const handleCompare = (e: React.FormEvent) => {
+ e.preventDefault();
+ const a = companyA.trim();
+ const b = companyB.trim();
+ if (a && b) {
+ setSearchParams({ a, b });
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ Portfolio Comparison
+
+
+ Compare patent portfolios of two companies side by side.
+
+
+
+ {/* Input Form */}
+
+
+ {/* Comparison Panels */}
+ {(queryA || queryB) && (
+
+ {queryA && (
+
+ )}
+ {queryB && (
+
+ )}
+
+ )}
+
+ );
+}