diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c20ca32..d7ec5ba 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -11,6 +11,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: {
@@ -43,6 +44,7 @@ function App() {
} />
} />
} />
+ } />
} />
{/* Admin routes */}
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index bf18963..d0df715 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -1,7 +1,7 @@
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { useTheme } from '../context/ThemeContext';
-import { Search, Layers, BarChart3, Info, Users, LogOut, Sun, Moon } from 'lucide-react';
+import { Search, Layers, BarChart3, Info, Users, LogOut, GitCompareArrows, Sun, Moon } from 'lucide-react';
export function Layout() {
const { user, isAdmin, logout } = useAuth();
@@ -17,6 +17,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 && (
+
+ )}
+
+ )}
+
+ );
+}