Files
ALwrity/frontend/src/components/SEODashboard/SEODashboard.tsx

1597 lines
73 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useRef, useState } from 'react';
import {
Box,
Container,
Grid,
Typography,
Alert,
Skeleton,
Chip,
Button,
IconButton,
Tooltip,
Menu,
MenuItem,
Divider,
Avatar,
Accordion,
AccordionSummary,
AccordionDetails,
CircularProgress,
} from '@mui/material';
import { motion, AnimatePresence } from 'framer-motion';
import { useAuth, useUser, SignOutButton, useClerk } from '@clerk/clerk-react';
import { apiClient } from '../../api/client';
import {
Refresh as RefreshIcon,
Person as PersonIcon,
ExitToApp as ExitIcon,
ArrowBack as ArrowBackIcon,
MoreVert as MoreVertIcon,
CheckCircle as CheckCircleIcon,
Schedule as ScheduleIcon,
Info as InfoIcon,
ExpandMore as ExpandMoreIcon,
AutoAwesome as AIIcon,
Tab as TabIcon,
} from '@mui/icons-material';
import { Tabs, Tab as MuiTab } from '@mui/material';
// Shared components
import { DashboardContainer, GlassCard } from '../shared/styled';
import SEOAnalyzerPanel from './components/SEOAnalyzerPanel';
import { SEOCopilotSuggestions } from './index';
import SEOCopilot from './SEOCopilot';
// Removed SEOCopilotTest
import useSEOCopilotStore from '../../stores/seoCopilotStore';
// Zustand store
import { useSEODashboardStore } from '../../stores/seoDashboardStore';
// API
import { userDataAPI } from '../../api/userData';
import { OnboardingScheduledTaskHealthResponse, OnboardingTaskStatus } from '../../api/seoDashboard';
// Shared components
import PlatformAnalytics from '../shared/PlatformAnalytics';
import { cachedAnalyticsAPI } from '../../api/cachedAnalytics';
// OAuth hooks
import { useBingOAuth } from '../../hooks/useBingOAuth';
import { useGSCConnection } from '../OnboardingWizard/common/useGSCConnection';
// SEO Dashboard component
import { SitemapBenchmarkResults } from '../OnboardingWizard/CompetitorAnalysisStep/SitemapBenchmarkResults';
import { StrategicInsightsResults } from '../OnboardingWizard/CompetitorAnalysisStep/StrategicInsightsResults';
import { AdvertoolsInsights } from './components/AdvertoolsInsights';
// Phase 2B: Semantic Dashboard components
import SemanticHealthCard from './components/SemanticHealthCard';
import SemanticInsights from './components/SemanticInsights';
// Phase 2A: Enterprise SEO Analysis
import SEOAnalysisController from './SEOAnalysisController';
const SEODashboard: React.FC = () => {
// Clerk authentication hooks
const { isSignedIn, isLoaded } = useAuth();
const { user } = useUser();
const { openSignIn } = useClerk();
// Zustand store hooks
const {
loading,
error,
data,
analysisData,
analysisLoading,
analysisError,
setData,
setLoading,
runSEOAnalysis,
refreshSEOAnalysis,
getAnalysisFreshness,
} = useSEODashboardStore();
// OAuth hooks
const { connect: connectBing } = useBingOAuth();
const { handleGSCConnect } = useGSCConnection();
// Platform status state
const [platformStatus, setPlatformStatus] = useState({
gsc: { connected: false, sites: [], last_sync: null, status: 'disconnected' },
bing: {
connected: false,
sites: [],
last_sync: null,
status: 'disconnected',
has_expired_tokens: false,
last_token_date: undefined,
total_tokens: 0
}
});
// Menu state
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
const [statusMenuAnchor, setStatusMenuAnchor] = useState<null | HTMLElement>(null);
// Dashboard Tab State for Enterprise Analysis
const [dashboardTab, setDashboardTab] = useState<number>(0);
// Competitor analysis data from onboarding step 3
const [competitorAnalysisData, setCompetitorAnalysisData] = useState<any>(null);
const [deepCompetitorAnalysisData, setDeepCompetitorAnalysisData] = useState<any>(null);
const [strategicInsightsHistory, setStrategicInsightsHistory] = useState<any[]>([]);
const [strategicInsightsLoading, setStrategicInsightsLoading] = useState(false);
const [competitiveSitemapBenchmarkingReport, setCompetitiveSitemapBenchmarkingReport] = useState<any>(null);
const [competitiveSitemapBenchmarkingLoading, setCompetitiveSitemapBenchmarkingLoading] = useState(false);
const [competitiveSitemapBenchmarkingError, setCompetitiveSitemapBenchmarkingError] = useState<string | null>(null);
const [onboardingTaskHealth, setOnboardingTaskHealth] = useState<OnboardingScheduledTaskHealthResponse | null>(null);
// PlatformAnalytics refresh handle
const platformRefreshRef = useRef<(() => Promise<void>) | null>(null);
// Sync dashboard analysis to Copilot store so readables have URL/context
const setCopilotAnalysisData = useSEOCopilotStore(state => state.setAnalysisData);
useEffect(() => {
if (analysisData) {
setCopilotAnalysisData(analysisData as any);
if (process.env.NODE_ENV === 'development') {
console.log('[CopilotSync] Pushed analysis to Copilot store', analysisData?.url);
}
}
}, [analysisData, setCopilotAnalysisData]);
// Load competitor analysis data on component mount
useEffect(() => {
loadCompetitorAnalysisData();
fetchStrategicInsightsHistory();
}, []);
const fetchStrategicInsightsHistory = async () => {
setStrategicInsightsLoading(true);
try {
const res = await apiClient.get('/api/seo-dashboard/strategic-insights/history');
if (res.data?.history?.length > 0) {
setStrategicInsightsHistory(res.data.history);
}
} catch (e) {
console.error("Failed to fetch strategic insights history", e);
} finally {
setStrategicInsightsLoading(false);
}
};
// Reconnect handlers using existing OAuth hooks
const handleGSCReconnect = async () => {
try {
console.log('Initiating GSC reconnect...');
await handleGSCConnect();
} catch (error) {
console.error('Error reconnecting GSC:', error);
}
};
const handleBingReconnect = async () => {
try {
console.log('Initiating Bing reconnect...');
// Purge expired tokens before reconnecting to avoid refresh loops
try {
await apiClient.post('/bing/purge-expired');
console.log('Purged expired Bing tokens before reconnect');
} catch (purgeError) {
console.warn('Failed to purge expired tokens (non-critical):', purgeError);
}
await connectBing();
// After successful reconnect, refresh platform status and run analysis
try {
// Invalidate backend analytics cache for Bing
try {
await apiClient.post('/api/analytics/cache/clear', null, { params: { platform: 'bing' } });
console.log('Cleared backend analytics cache for Bing');
} catch (cacheErr) {
console.warn('Failed to clear backend analytics cache (non-critical):', cacheErr);
}
// Invalidate frontend cached analytics
try {
cachedAnalyticsAPI.invalidatePlatformStatus();
// Optional: clear all analytics cache if available
// @ts-ignore - method may not exist in older builds
cachedAnalyticsAPI.clearCache?.();
console.log('Cleared frontend analytics cache');
} catch (feCacheErr) {
console.warn('Failed to clear frontend analytics cache (non-critical):', feCacheErr);
}
await fetchPlatformStatus();
} catch (e) {
console.warn('Post-reconnect platform status refresh failed:', e);
}
try {
await useSEODashboardStore.getState().refreshSEOAnalysis();
} catch (e) {
console.warn('Post-reconnect analysis refresh failed:', e);
}
// Force PlatformAnalytics to refresh (bypass cache)
try {
await platformRefreshRef.current?.();
} catch (e) {
console.warn('Platform analytics forced refresh failed (non-critical):', e);
}
} catch (error) {
console.error('Error reconnecting Bing:', error);
}
};
// One-run guard to avoid duplicate fetches under StrictMode
const dataFetchedRef = useRef(false);
// Consolidated data fetching effect
useEffect(() => {
if (dataFetchedRef.current || !isSignedIn) return;
dataFetchedRef.current = true;
const fetchAllData = async () => {
let websiteUrl = 'https://alwrity.com'; // Default fallback
try {
setLoading(true);
// Fetch platform status and user data in parallel
const [platformResponse, userData, onboardingTaskHealthResponse] = await Promise.all([
apiClient.get('/api/seo-dashboard/platforms'),
userDataAPI.getUserData(),
apiClient.get('/api/seo-dashboard/onboarding-task-health')
]);
console.log('Platform status response:', platformResponse.status, platformResponse.statusText);
console.log('Platform status data:', platformResponse.data);
setPlatformStatus(platformResponse.data);
setOnboardingTaskHealth(onboardingTaskHealthResponse.data);
websiteUrl = userData?.website_url || 'https://alwrity.com';
// Fetch real data from backend using authenticated API client
console.log('Fetching SEO dashboard overview...');
const response = await apiClient.get('/api/seo-dashboard/overview', {
params: { site_url: websiteUrl }
});
console.log('SEO overview response:', response.status, response.statusText);
console.log('Real SEO data received:', response.data);
setData(response.data);
try {
const deepResponse = await apiClient.get('/api/seo-dashboard/deep-competitor-analysis', {
params: { site_url: websiteUrl }
});
setDeepCompetitorAnalysisData(deepResponse.data);
} catch (e) {
console.warn('Deep competitor analysis not available yet:', e);
setDeepCompetitorAnalysisData(null);
}
try {
const sitemapBenchResponse = await apiClient.get('/api/seo/competitive-sitemap-benchmarking');
const report = sitemapBenchResponse?.data?.data?.report ?? null;
setCompetitiveSitemapBenchmarkingReport(report);
} catch (e) {
console.warn('Competitive sitemap benchmarking not available yet:', e);
setCompetitiveSitemapBenchmarkingReport(null);
}
try {
setStrategicInsightsLoading(true);
const strategicHistoryRes = await apiClient.get('/api/seo-dashboard/strategic-insights/history');
setStrategicInsightsHistory(strategicHistoryRes.data?.history || []);
} catch (e) {
console.warn('Strategic insights history not available yet:', e);
} finally {
setStrategicInsightsLoading(false);
}
} catch (error) {
console.error('Error fetching SEO dashboard data:', error);
// Fallback to mock data on error
const mockData = {
health_score: {
score: 84,
change: 5,
trend: 'up',
label: 'EXCELLENT',
color: '#4CAF50'
},
key_insight: 'Your website has excellent technical SEO foundation with room for improvement',
priority_alert: 'Mobile page speed could be optimized further',
metrics: {
traffic: { value: 12500, change: 15, trend: 'up', description: 'Organic traffic', color: '#4CAF50' },
rankings: { value: 8.5, change: 2.3, trend: 'up', description: 'Average ranking', color: '#2196F3' },
mobile: { value: 92, change: -3, trend: 'down', description: 'Mobile speed', color: '#FF9800' },
keywords: { value: 150, change: 12, trend: 'up', description: 'Keywords tracked', color: '#9C27B0' }
},
platforms: {
google: { status: 'connected', connected: true, last_sync: '2024-01-15T10:30:00Z', data_points: 1250 },
bing: { status: 'connected', connected: true, last_sync: '2024-01-15T09:45:00Z', data_points: 850 },
yandex: { status: 'disconnected', connected: false }
},
ai_insights: [
{
insight: 'Your website has excellent technical SEO foundation',
priority: 'low',
category: 'technical',
action_required: false
},
{
insight: 'Consider adding more internal links to improve page authority',
priority: 'medium',
category: 'content',
action_required: false
},
{
insight: 'Mobile page speed could be optimized further',
priority: 'high',
category: 'performance',
action_required: true,
tool_path: '/seo-dashboard'
}
],
last_updated: new Date().toISOString(),
website_url: websiteUrl || undefined // Convert null to undefined for TypeScript
};
setData(mockData);
setDeepCompetitorAnalysisData(null);
setCompetitiveSitemapBenchmarkingReport(null);
} finally {
setLoading(false);
}
};
fetchAllData();
}, [isSignedIn, setLoading, setData]);
useEffect(() => {
// Run initial SEO analysis if no data exists
if (!loading && !error && data) {
// Check if we have cached analysis data first
const store = useSEODashboardStore.getState();
store.checkAndRunInitialAnalysis();
// If no cached analysis data and we have a website URL, run initial analysis
if (!store.analysisData && data.website_url) {
console.log('No cached analysis data found, running initial SEO analysis...');
store.runSEOAnalysis();
}
}
}, [loading, error, data]);
// Menu handlers
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setUserMenuAnchor(event.currentTarget);
};
const handleUserMenuClose = () => {
setUserMenuAnchor(null);
};
const handleStatusMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setStatusMenuAnchor(event.currentTarget);
};
const handleStatusMenuClose = () => {
setStatusMenuAnchor(null);
};
const handleBackToDashboard = () => {
window.location.href = '/seo-dashboard';
};
const handleRefreshData = async () => {
try {
setLoading(true);
await refreshSEOAnalysis();
await fetchPlatformStatus();
} catch (error) {
console.error('Error refreshing data:', error);
} finally {
setLoading(false);
}
};
const runStrategicInsights = async () => {
setStrategicInsightsLoading(true);
try {
const res = await apiClient.post('/api/seo-dashboard/strategic-insights/run');
if (res.data?.success) {
setStrategicInsightsHistory(prev => [res.data.report, ...prev]);
}
} catch (e: any) {
console.error('Failed to run strategic insights:', e);
} finally {
setStrategicInsightsLoading(false);
}
};
// Background jobs visibility (user-triggered)
const [showBackgroundJobs, setShowBackgroundJobs] = useState(false);
// Platform status fetching function
const fetchPlatformStatus = async () => {
try {
console.log('Fetching platform status...');
const response = await apiClient.get('/api/seo-dashboard/platforms');
console.log('Platform status response:', response.status, response.statusText);
console.log('Platform status data:', response.data);
setPlatformStatus(response.data);
} catch (error) {
console.error('Error fetching platform status:', error);
}
};
// Load competitor analysis data from onboarding step 3
const loadCompetitorAnalysisData = () => {
try {
const cachedData = localStorage.getItem('competitor_analysis_data');
const cachedUrl = localStorage.getItem('competitor_analysis_url');
const cachedTimestamp = localStorage.getItem('competitor_analysis_timestamp');
if (cachedData && cachedUrl && cachedTimestamp) {
const analysisData = JSON.parse(cachedData);
const timestamp = parseInt(cachedTimestamp);
const isRecent = (Date.now() - timestamp) < (7 * 24 * 60 * 60 * 1000); // 7 days
if (isRecent) {
console.log('Loading competitor analysis data from onboarding step 3:', analysisData);
setCompetitorAnalysisData(analysisData);
} else {
console.log('Competitor analysis data is too old, not loading');
}
} else {
console.log('No competitor analysis data found in localStorage');
}
} catch (error) {
console.error('Error loading competitor analysis data:', error);
}
};
const runCompetitiveSitemapBenchmarking = async () => {
setCompetitiveSitemapBenchmarkingError(null);
setCompetitiveSitemapBenchmarkingLoading(true);
try {
await apiClient.post('/api/seo/competitive-sitemap-benchmarking/run', { max_competitors: null });
const sitemapBenchResponse = await apiClient.get('/api/seo/competitive-sitemap-benchmarking');
const report = sitemapBenchResponse?.data?.data?.report ?? null;
setCompetitiveSitemapBenchmarkingReport(report);
} catch (e: any) {
setCompetitiveSitemapBenchmarkingError(e?.response?.data?.detail || e?.message || 'Failed to run benchmark');
} finally {
setCompetitiveSitemapBenchmarkingLoading(false);
}
};
if (loading) {
return <Skeleton variant="rectangular" height={200} />;
}
if (error || !data) {
return <Alert severity="error">Failed to load dashboard data</Alert>;
}
// Show sign-in prompt if not authenticated
const statusUiMap: Record<OnboardingTaskStatus, { label: string; color: string; bg: string; border: string; action: string }> = {
active: { label: 'Active', color: '#22c55e', bg: 'rgba(34,197,94,0.12)', border: 'rgba(34,197,94,0.4)', action: 'No action needed. Monitor next execution to confirm regular runs.' },
failed: { label: 'Failed', color: '#ef4444', bg: 'rgba(239,68,68,0.12)', border: 'rgba(239,68,68,0.4)', action: 'Review the latest error and rerun after fixing data/source issues.' },
paused: { label: 'Paused', color: '#f59e0b', bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.4)', action: 'Resume this task from scheduler controls when ready.' },
needs_intervention: { label: 'Needs intervention', color: '#f97316', bg: 'rgba(249,115,22,0.12)', border: 'rgba(249,115,22,0.4)', action: 'Immediate action required. Inspect failures and reconfigure before retrying.' },
not_scheduled: { label: 'Not scheduled', color: '#94a3b8', bg: 'rgba(148,163,184,0.12)', border: 'rgba(148,163,184,0.4)', action: 'Complete onboarding scheduling or create the task in scheduler.' }
};
const orderedTaskKeys = [
'OnboardingFullWebsiteAnalysisTask',
'DeepCompetitorAnalysisTask',
'SIFIndexingTask',
'MarketTrendsTask'
];
if (!isLoaded) {
return <Skeleton variant="rectangular" height={200} />;
}
if (!isSignedIn) {
return (
<DashboardContainer>
<Container maxWidth="md">
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '60vh',
textAlign: 'center',
gap: 3
}}>
<Typography variant="h4" sx={{ color: 'white', fontWeight: 700 }}>
🔍 SEO Dashboard
</Typography>
<Typography variant="h6" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
Sign in to access your SEO analytics and Google Search Console data
</Typography>
<Button
onClick={() => openSignIn({ forceRedirectUrl: '/seo-dashboard' })}
variant="contained"
size="large"
sx={{
bgcolor: '#4285f4',
'&:hover': { bgcolor: '#3367d6' },
px: 4,
py: 1.5,
fontSize: '1.1rem',
fontWeight: 600
}}
>
Sign In to Continue
</Button>
</Box>
</Container>
</DashboardContainer>
);
}
return (
<DashboardContainer>
<Container maxWidth="xl">
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
{/* Professional Compact Header */}
<Box sx={{
mb: 4,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
py: 2,
px: 3,
bgcolor: 'rgba(255, 255, 255, 0.05)',
borderRadius: 2,
border: '1px solid rgba(255, 255, 255, 0.1)'
}}>
{/* Left Section - Navigation & Title */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<IconButton
onClick={handleBackToDashboard}
sx={{
color: 'white',
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
}}
>
<ArrowBackIcon />
</IconButton>
<Box>
<Typography variant="h5" sx={{ color: 'white', fontWeight: 700, lineHeight: 1.2 }}>
SEO Dashboard
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
AI-powered insights and recommendations
</Typography>
</Box>
</Box>
{/* Center Section - Status Overview */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Tooltip title="Platform Connection Status">
<IconButton
onClick={handleStatusMenuOpen}
sx={{
color: 'white',
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
}}
>
<CheckCircleIcon sx={{
color: platformStatus.gsc.connected && platformStatus.bing.connected
? '#4CAF50'
: platformStatus.gsc.connected || platformStatus.bing.connected
? '#FF9800'
: '#f44336'
}} />
</IconButton>
</Tooltip>
<Tooltip title="Data Freshness">
<Chip
icon={<ScheduleIcon />}
label={(() => {
const freshness = getAnalysisFreshness();
return freshness.label;
})()}
size="small"
sx={{
bgcolor: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
fontSize: '0.75rem'
}}
/>
</Tooltip>
{onboardingTaskHealth && (
<Tooltip title="Onboarding Scheduled SEO Tasks">
<Chip
label={`Onboarding Tasks: ${Object.values(onboardingTaskHealth.tasks || {}).filter((task: any) => task?.status === 'active').length} active`}
size="small"
sx={{
ml: 2,
bgcolor: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)'
}}
/>
</Tooltip>
)}
</Box>
{/* Right Section - User Menu */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Avatar sx={{ width: 32, height: 32, bgcolor: 'rgba(33, 150, 243, 0.8)' }}>
<PersonIcon fontSize="small" />
</Avatar>
<IconButton
onClick={handleUserMenuOpen}
sx={{
color: 'white',
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' }
}}
>
<MoreVertIcon />
</IconButton>
</Box>
{/* Status Menu */}
<Menu
anchorEl={statusMenuAnchor}
open={Boolean(statusMenuAnchor)}
onClose={handleStatusMenuClose}
PaperProps={{
sx: {
bgcolor: 'rgba(30, 30, 30, 0.95)',
border: '1px solid rgba(255, 255, 255, 0.1)',
color: 'white',
minWidth: 280
}
}}
>
<MenuItem disabled>
<Typography variant="subtitle2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
Platform Status
</Typography>
</MenuItem>
{/* GSC Status */}
<MenuItem>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CheckCircleIcon sx={{
color: platformStatus.gsc.connected ? '#4CAF50' : '#f44336',
fontSize: 16
}} />
<Typography variant="body2">
Google Search Console: {platformStatus.gsc.connected ? 'Connected' : 'Disconnected'}
</Typography>
</Box>
{!platformStatus.gsc.connected && (
<Button
size="small"
variant="outlined"
onClick={handleGSCReconnect}
sx={{
ml: 2,
borderColor: 'rgba(255, 255, 255, 0.3)',
color: 'white',
fontSize: '0.75rem',
'&:hover': {
borderColor: 'rgba(255, 255, 255, 0.5)',
bgcolor: 'rgba(255, 255, 255, 0.1)'
}
}}
>
Reconnect
</Button>
)}
</Box>
</MenuItem>
{/* Bing Status */}
<MenuItem>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CheckCircleIcon sx={{
color: platformStatus.bing.connected ? '#4CAF50' :
platformStatus.bing.status === 'expired' ? '#FF9800' : '#f44336',
fontSize: 16
}} />
<Box>
<Typography variant="body2">
Bing Webmaster: {platformStatus.bing.connected ? 'Connected' :
platformStatus.bing.status === 'expired' ? 'Expired' : 'Disconnected'}
</Typography>
{platformStatus.bing.status === 'expired' && platformStatus.bing.last_token_date && (
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '0.7rem' }}>
Last connected: {new Date(platformStatus.bing.last_token_date).toLocaleDateString()}
</Typography>
)}
</Box>
</Box>
{!platformStatus.bing.connected && (
<Button
size="small"
variant="outlined"
onClick={handleBingReconnect}
sx={{
ml: 2,
borderColor: platformStatus.bing.status === 'expired' ? '#FF9800' : 'rgba(255, 255, 255, 0.3)',
color: platformStatus.bing.status === 'expired' ? '#FF9800' : 'white',
fontSize: '0.75rem',
'&:hover': {
borderColor: platformStatus.bing.status === 'expired' ? '#FFB74D' : 'rgba(255, 255, 255, 0.5)',
bgcolor: platformStatus.bing.status === 'expired' ? 'rgba(255, 152, 0, 0.1)' : 'rgba(255, 255, 255, 0.1)'
}
}}
>
{platformStatus.bing.status === 'expired' ? 'Reconnect' : 'Connect'}
</Button>
)}
</Box>
</MenuItem>
</Menu>
{/* User Menu */}
<Menu
anchorEl={userMenuAnchor}
open={Boolean(userMenuAnchor)}
onClose={handleUserMenuClose}
PaperProps={{
sx: {
bgcolor: 'rgba(30, 30, 30, 0.95)',
border: '1px solid rgba(255, 255, 255, 0.1)',
color: 'white'
}
}}
>
<MenuItem disabled>
<Typography variant="subtitle2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
{user?.primaryEmailAddress?.emailAddress || 'User'}
</Typography>
</MenuItem>
<Divider sx={{ bgcolor: 'rgba(255, 255, 255, 0.1)' }} />
<MenuItem onClick={handleRefreshData}>
<RefreshIcon sx={{ mr: 1, fontSize: 16 }} />
<Typography variant="body2">Refresh Data</Typography>
</MenuItem>
<Divider sx={{ bgcolor: 'rgba(255, 255, 255, 0.1)' }} />
<SignOutButton>
<MenuItem>
<ExitIcon sx={{ mr: 1, fontSize: 16 }} />
<Typography variant="body2">Sign Out</Typography>
</MenuItem>
</SignOutButton>
</Menu>
</Box>
{/* CopilotKit Test Panel removed */}
{/* Dashboard Tabs */}
<Box sx={{ mb: 4, display: 'flex', gap: 1, borderBottom: '1px solid rgba(255, 255, 255, 0.1)', pb: 1 }}>
<Button
variant={dashboardTab === 0 ? 'contained' : 'text'}
onClick={() => setDashboardTab(0)}
sx={{
color: dashboardTab === 0 ? 'white' : 'rgba(255, 255, 255, 0.7)',
bgcolor: dashboardTab === 0 ? 'rgba(33, 150, 243, 0.3)' : 'transparent',
borderBottom: dashboardTab === 0 ? '2px solid #2196F3' : 'none',
borderRadius: 0,
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.05)' }
}}
>
📊 Overview
</Button>
<Button
variant={dashboardTab === 1 ? 'contained' : 'text'}
onClick={() => setDashboardTab(1)}
sx={{
color: dashboardTab === 1 ? 'white' : 'rgba(255, 255, 255, 0.7)',
bgcolor: dashboardTab === 1 ? 'rgba(33, 150, 243, 0.3)' : 'transparent',
borderBottom: dashboardTab === 1 ? '2px solid #2196F3' : 'none',
borderRadius: 0,
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.05)' }
}}
>
🔍 Enterprise Analysis
</Button>
</Box>
{/* Tab Content: Overview */}
{dashboardTab === 0 && (
<>
{/* Search Performance Overview */}
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
📊 Search Performance Overview
</Typography>
<Tooltip title="Real-time analytics data from connected search platforms">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
<Box sx={{ flexGrow: 1 }} />
<Button
variant="outlined"
size="small"
onClick={() => setShowBackgroundJobs((v) => !v)}
sx={{ textTransform: 'none' }}
>
{showBackgroundJobs ? 'Hide Background Jobs' : 'Run Background Jobs'}
</Button>
</Box>
<PlatformAnalytics
platforms={['gsc', 'bing']}
showSummary={true}
refreshInterval={0}
onDataLoaded={(analyticsData) => {
console.log('Real analytics data loaded:', analyticsData);
}}
onRefreshReady={(fn) => { platformRefreshRef.current = fn; }}
onReconnect={(platform) => {
if (platform === 'gsc') {
handleGSCReconnect();
} else if (platform === 'bing') {
handleBingReconnect();
}
}}
showBackgroundJobs={showBackgroundJobs}
/>
{/* Enhanced Metrics with Tooltips */}
<Box sx={{ mt: 3 }}>
<Grid container spacing={2}>
<Grid item xs={6} sm={3}>
<Tooltip title="Number of search engine platforms (GSC, Bing) currently connected to your dashboard">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Connected Platforms
</Typography>
<Typography variant="h4" sx={{ color: '#4CAF50', fontWeight: 700 }}>
{(platformStatus.gsc.connected ? 1 : 0) + (platformStatus.bing.connected ? 1 : 0)}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
of 2 platforms
</Typography>
</GlassCard>
</Tooltip>
</Grid>
<Grid item xs={6} sm={3}>
<Tooltip title="Total number of clicks from search results to your website within the selected time period">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Total Clicks
</Typography>
<Typography variant="h4" sx={{ color: '#2196F3', fontWeight: 700 }}>
{data.metrics?.traffic?.value || data.summary?.clicks || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
from search results
</Typography>
</GlassCard>
</Tooltip>
</Grid>
<Grid item xs={6} sm={3}>
<Tooltip title="Total number of times your website appeared in search results within the selected time period">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Total Impressions
</Typography>
<Typography variant="h4" sx={{ color: '#FF9800', fontWeight: 700 }}>
{data.metrics?.impressions?.value || data.summary?.impressions || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
search appearances
</Typography>
</GlassCard>
</Tooltip>
</Grid>
<Grid item xs={6} sm={3}>
<Tooltip title="Percentage of impressions that resulted in a click to your website (Clicks ÷ Impressions × 100)">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Overall CTR
</Typography>
<Typography variant="h4" sx={{ color: '#9C27B0', fontWeight: 700 }}>
{data.metrics?.ctr?.value || data.summary?.ctr || 0}%
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
click-through rate
</Typography>
</GlassCard>
</Tooltip>
</Grid>
</Grid>
</Box>
</Box>
{/* Full Site Technical SEO Audit (from onboarding background job) */}
{data.technical_seo_audit && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🧩 Technical SEO Audit
</Typography>
<Tooltip title="Full-site audit runs automatically after onboarding. Low-scoring pages are marked as Fix Scheduled.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
<Box sx={{ flexGrow: 1 }} />
{data.technical_seo_audit.status === 'scheduled' && (
<Chip
icon={<ScheduleIcon />}
label={`Scheduled${data.technical_seo_audit.next_execution ? `${new Date(data.technical_seo_audit.next_execution).toLocaleString()}` : ''}`}
sx={{ bgcolor: 'rgba(255, 193, 7, 0.15)', color: '#FFC107' }}
/>
)}
{data.technical_seo_audit.status === 'ready' && (
<Chip
icon={<CheckCircleIcon />}
label="Results Available"
sx={{ bgcolor: 'rgba(76, 175, 80, 0.15)', color: '#4CAF50' }}
/>
)}
{data.technical_seo_audit.status === 'error' && (
<Chip
label="Audit Error"
sx={{ bgcolor: 'rgba(244, 67, 54, 0.15)', color: '#F44336' }}
/>
)}
</Box>
{data.technical_seo_audit.status === 'scheduled' && (
<Alert severity="info" sx={{ mb: 2 }}>
Full-site audit runs automatically after onboarding. This may take a few minutes depending on how many pages we discover.
</Alert>
)}
<Grid container spacing={2}>
<Grid item xs={12} sm={4}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Pages Audited
</Typography>
<Typography variant="h4" sx={{ color: 'white', fontWeight: 700 }}>
{data.technical_seo_audit.pages_audited}
</Typography>
</GlassCard>
</Grid>
<Grid item xs={12} sm={4}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Average Score
</Typography>
<Typography variant="h4" sx={{ color: '#2196F3', fontWeight: 700 }}>
{data.technical_seo_audit.avg_score}/100
</Typography>
</GlassCard>
</Grid>
<Grid item xs={12} sm={4}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Fix Scheduled
</Typography>
<Typography variant="h4" sx={{ color: '#FF9800', fontWeight: 700 }}>
{data.technical_seo_audit.fix_scheduled_pages}
</Typography>
</GlassCard>
</Grid>
</Grid>
{data.technical_seo_audit.worst_pages?.length > 0 && (
<Box sx={{ mt: 2 }}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 600, mb: 1 }}>
Lowest Scoring Pages
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{data.technical_seo_audit.worst_pages.slice(0, 5).map((p) => (
<Box
key={p.page_url}
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 2 }}
>
<Typography
variant="body2"
sx={{ color: 'rgba(255, 255, 255, 0.85)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
title={p.page_url}
>
{p.page_url}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip
size="small"
label={`${p.overall_score}/100`}
sx={{ bgcolor: 'rgba(33, 150, 243, 0.15)', color: '#90CAF9' }}
/>
<Chip
size="small"
label={p.status === 'fix_scheduled' ? 'Fix Scheduled' : p.status}
sx={{ bgcolor: 'rgba(255, 152, 0, 0.15)', color: '#FFB74D' }}
/>
</Box>
</Box>
))}
</Box>
</GlassCard>
</Box>
)}
</Box>
)}
{/* Data-Driven Content Intelligence (Advertools) */}
{data.advertools_insights && (
<AdvertoolsInsights data={data.advertools_insights} />
)}
{/* Competitive Analysis from Onboarding Step 3 */}
{competitorAnalysisData && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🎯 Competitive Analysis
</Typography>
<Tooltip title="Real competitor analysis data from onboarding step 3">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
</Box>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Tooltip title="Number of competitors discovered during onboarding analysis">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Competitors Found
</Typography>
<Typography variant="h4" sx={{ color: '#4CAF50', fontWeight: 700 }}>
{competitorAnalysisData.competitors?.length || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
in your market
</Typography>
</GlassCard>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip title="Social media accounts discovered for competitors">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Social Media Accounts
</Typography>
<Typography variant="h4" sx={{ color: '#2196F3', fontWeight: 700 }}>
{Object.keys(competitorAnalysisData.social_media_accounts || {}).length}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
competitor accounts
</Typography>
</GlassCard>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip title="Social media citations and mentions found">
<GlassCard sx={{ p: 2, cursor: 'help' }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Social Citations
</Typography>
<Typography variant="h4" sx={{ color: '#FF9800', fontWeight: 700 }}>
{competitorAnalysisData.social_media_citations?.length || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
mentions found
</Typography>
</GlassCard>
</Tooltip>
</Grid>
</Grid>
{/* Competitor List */}
{competitorAnalysisData.competitors && competitorAnalysisData.competitors.length > 0 && (
<Box sx={{ mt: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
Top Competitors
</Typography>
<Grid container spacing={2}>
{competitorAnalysisData.competitors.slice(0, 6).map((competitor: any, index: number) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 600, mb: 1 }}>
{competitor.name || competitor.domain || `Competitor ${index + 1}`}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
{competitor.domain || competitor.url || 'No domain available'}
</Typography>
{competitor.description && (
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
{competitor.description.length > 100
? `${competitor.description.substring(0, 100)}...`
: competitor.description}
</Typography>
)}
</GlassCard>
</Grid>
))}
</Grid>
</Box>
)}
{/* Research Summary */}
{competitorAnalysisData.research_summary && (
<Box sx={{ mt: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
Research Summary
</Typography>
<GlassCard sx={{ p: 3 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.9)', lineHeight: 1.6 }}>
{competitorAnalysisData.research_summary}
</Typography>
</GlassCard>
</Box>
)}
</Box>
)}
{/* Strategic Insights (Winning Moves) */}
{strategicInsightsHistory.length > 0 && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🏆 Strategic Insights (Winning Moves)
</Typography>
<Tooltip title="AI-generated weekly strategic briefs to outperform competitors.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
<Box sx={{ flexGrow: 1 }} />
<Chip
label={`Latest: ${new Date(strategicInsightsHistory[0].generated_at).toLocaleDateString()}`}
size="small"
sx={{ bgcolor: 'rgba(139, 92, 246, 0.15)', color: '#a78bfa' }}
/>
</Box>
<StrategicInsightsResults
report={strategicInsightsHistory[0]}
hideCreateContent={false}
/>
</Box>
)}
{/* Phase 2B: Semantic Intelligence Dashboard */}
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🧠 Semantic Intelligence
</Typography>
<Tooltip title="Real-time semantic analysis powered by AI. Updates every 24 hours.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
</Box>
{/* Semantic Health Overview */}
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<SemanticHealthCard compact />
</Grid>
<Grid item xs={12} md={6}>
{/* Placeholder for additional semantic metrics */}
<SemanticInsights maxInsights={2} />
</Grid>
</Grid>
{/* Full Semantic Dashboard */}
<SemanticInsights />
</Box>
{/* Deep Competitor Analysis (auto-scheduled) */}
{deepCompetitorAnalysisData && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🔍 Deep Competitor Analysis
</Typography>
<Tooltip title="Auto-scheduled after onboarding completion. Uses Step 2 website insights and Step 3 competitors.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
</Box>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Status
</Typography>
<Chip
size="small"
label={(deepCompetitorAnalysisData.status || 'unknown').toString()}
sx={{ bgcolor: 'rgba(34, 197, 94, 0.15)', color: '#86efac', fontWeight: 700 }}
/>
{deepCompetitorAnalysisData.last_status && (
<Typography variant="caption" sx={{ display: 'block', mt: 1, color: 'rgba(255, 255, 255, 0.6)' }}>
Last run: {deepCompetitorAnalysisData.last_status}
</Typography>
)}
</GlassCard>
</Grid>
<Grid item xs={12} md={4}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Competitors
</Typography>
<Typography variant="h4" sx={{ color: '#4CAF50', fontWeight: 700 }}>
{deepCompetitorAnalysisData.competitors_count ?? (deepCompetitorAnalysisData.report?.competitors?.length || 0)}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
analyzed
</Typography>
</GlassCard>
</Grid>
<Grid item xs={12} md={4}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
Schedule
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<ScheduleIcon sx={{ color: 'rgba(255,255,255,0.7)', fontSize: 18 }} />
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.85)' }}>
{deepCompetitorAnalysisData.next_execution
? deepCompetitorAnalysisData.next_execution
: (deepCompetitorAnalysisData.last_run ? 'Completed' : 'Pending')}
</Typography>
</Box>
{deepCompetitorAnalysisData.last_run && (
<Typography variant="caption" sx={{ display: 'block', mt: 1, color: 'rgba(255, 255, 255, 0.6)' }}>
Last run: {deepCompetitorAnalysisData.last_run}
</Typography>
)}
</GlassCard>
</Grid>
</Grid>
{!deepCompetitorAnalysisData.report && (
<Box sx={{ mt: 3 }}>
<Alert severity="info">
Deep competitor analysis is scheduled or running. Once complete, the full per-competitor extraction, AI analysis, and aggregated insights will appear here.
</Alert>
</Box>
)}
{deepCompetitorAnalysisData.report?.aggregation && (
<Box sx={{ mt: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
Aggregated Insights
</Typography>
<GlassCard sx={{ p: 3 }}>
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.9)', fontWeight: 700, mb: 1 }}>
Common Themes
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)', mb: 2 }}>
{(deepCompetitorAnalysisData.report.aggregation.common_patterns?.common_themes || []).slice(0, 8).join(' • ') || '—'}
</Typography>
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.9)', fontWeight: 700, mb: 1 }}>
Top Opportunities
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)', mb: 2 }}>
{(deepCompetitorAnalysisData.report.aggregation.content_gaps_and_opportunities || [])
.slice(0, 5)
.map((g: any) => g.gap)
.filter(Boolean)
.join(' • ') || '—'}
</Typography>
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.9)', fontWeight: 700, mb: 1 }}>
Recommended Actions
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
{(deepCompetitorAnalysisData.report.aggregation.strategic_recommendations || [])
.slice(0, 5)
.map((r: any) => r.action)
.filter(Boolean)
.join(' • ') || '—'}
</Typography>
</GlassCard>
</Box>
)}
{deepCompetitorAnalysisData.report?.competitors?.length > 0 && (
<Box sx={{ mt: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600, mb: 2 }}>
Per-Competitor Details
</Typography>
{deepCompetitorAnalysisData.report.competitors.slice(0, 25).map((c: any, idx: number) => {
const input = c?.input || {};
const extraction = c?.extraction || {};
const ai = c?.ai_analysis || {};
const title = input.name || input.domain || `Competitor ${idx + 1}`;
const domain = input.domain || input.url || '';
return (
<Accordion key={`${domain}-${idx}`} sx={{ bgcolor: 'rgba(255,255,255,0.06)', mb: 1, borderRadius: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon sx={{ color: 'rgba(255,255,255,0.8)' }} />}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 700 }}>
{title}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
{domain}
</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 700, mb: 1 }}>
Extraction
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)', mb: 1 }}>
{extraction.page_meta?.title || '—'}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
{(extraction.page_meta?.meta_description || '').slice(0, 220) || '—'}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)', mt: 2 }}>
CTA signals: {(extraction.signals?.cta_signals?.keyword_hits || []).slice(0, 8).join(', ') || '—'}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)', mt: 1 }}>
Proof signals: {(extraction.signals?.proof_signals?.keyword_hits || []).slice(0, 6).join(', ') || '—'}
</Typography>
</GlassCard>
</Grid>
<Grid item xs={12} md={6}>
<GlassCard sx={{ p: 2 }}>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 700, mb: 1 }}>
AI Analysis
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.85)', mb: 1 }}>
Value prop: {ai.positioning?.value_prop || '—'}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.85)', mb: 1 }}>
Primary offer: {ai.positioning?.primary_offer || '—'}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.85)', mb: 1 }}>
Themes: {(ai.content_strategy?.themes || []).slice(0, 6).join(' • ') || '—'}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.85)' }}>
Opportunities vs you: {(ai.comparison_to_user_baseline?.opportunities || []).slice(0, 4).join(' • ') || '—'}
</Typography>
</GlassCard>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
})}
</Box>
)}
</Box>
)}
{/* Weekly Strategic Brief */}
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🧠 Weekly Strategy Brief
</Typography>
<Tooltip title="AI-powered strategic insights based on competitor content velocity and market shifts.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
</Box>
<Button
variant="contained"
startIcon={strategicInsightsLoading ? <CircularProgress size={20} color="inherit" /> : <AIIcon />}
onClick={runStrategicInsights}
disabled={strategicInsightsLoading}
sx={{
bgcolor: '#8b5cf6',
'&:hover': { bgcolor: '#7c3aed' },
textTransform: 'none',
fontWeight: 700
}}
>
{strategicInsightsLoading ? 'Analyzing...' : 'Run Analysis Now'}
</Button>
</Box>
{strategicInsightsHistory.length > 0 ? (
<GlassCard sx={{ p: 0, overflow: 'hidden', border: 'none', bgcolor: 'transparent' }}>
<StrategicInsightsResults report={strategicInsightsHistory[0]} />
</GlassCard>
) : (
<GlassCard sx={{ p: 4, textAlign: 'center' }}>
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.7)', mb: 2 }}>
No strategic insights generated yet. Run your first analysis to see "The Big Move" and market opportunities.
</Typography>
<Button
variant="outlined"
onClick={runStrategicInsights}
sx={{ color: 'white', borderColor: 'rgba(255,255,255,0.3)' }}
>
Get Started
</Button>
</GlassCard>
)}
</Box>
{(competitiveSitemapBenchmarkingReport || competitorAnalysisData) && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 2, mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🗺 Competitive Sitemap Benchmarking (No AI)
</Typography>
<Tooltip title="Uses public sitemaps and deterministic rules (no LLM calls) to compare structure, coverage, and publishing signals.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
</Box>
<Button
variant="contained"
onClick={runCompetitiveSitemapBenchmarking}
disabled={competitiveSitemapBenchmarkingLoading}
sx={{
bgcolor: '#10b981',
'&:hover': { bgcolor: '#059669' },
textTransform: 'none',
fontWeight: 700
}}
>
{competitiveSitemapBenchmarkingLoading ? 'Running…' : 'Run Benchmark'}
</Button>
</Box>
{competitiveSitemapBenchmarkingError && (
<Box sx={{ mb: 2 }}>
<Alert severity="error">{competitiveSitemapBenchmarkingError}</Alert>
</Box>
)}
{!competitiveSitemapBenchmarkingReport && (
<Box sx={{ mt: 2 }}>
<Alert severity="info">
No benchmarking report yet. Run it to compare your sitemap structure against competitors and discover missing sections.
</Alert>
</Box>
)}
{competitiveSitemapBenchmarkingReport && competitiveSitemapBenchmarkingReport.benchmark && (
<SitemapBenchmarkResults
data={{
user_summary: competitiveSitemapBenchmarkingReport.benchmark.user?.summary || {},
competitor_summaries: competitiveSitemapBenchmarkingReport.benchmark.competitors?.summaries || {},
timestamp: competitiveSitemapBenchmarkingReport.timestamp,
benchmark: competitiveSitemapBenchmarkingReport.benchmark
}}
/>
)}
</Box>
)}
{/* Strategic Insights Section */}
{strategicInsightsHistory.length > 0 && (
<Box sx={{ mb: 4 }} id="strategic-insights-results">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
🧠 AI-Powered Strategic Insights
</Typography>
<Tooltip title="Weekly strategic briefs generated by AI analysis of competitor content moves and market shifts.">
<InfoIcon sx={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 18 }} />
</Tooltip>
</Box>
<StrategicInsightsResults report={strategicInsightsHistory[0]} />
</Box>
)}
{onboardingTaskHealth && (
<Box sx={{ mt: 4 }}>
<GlassCard>
<Box sx={{ p: 3 }}>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 700, mb: 0.5 }}>
Onboarding Scheduled SEO Tasks
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)', mb: 2 }}>
Unified health view for onboarding automation jobs.
</Typography>
<Grid container spacing={2}>
{orderedTaskKeys.map((taskKey) => {
const task = onboardingTaskHealth.tasks?.[taskKey];
if (!task) return null;
const status = (task.status || 'not_scheduled') as OnboardingTaskStatus;
const ui = statusUiMap[status] || statusUiMap.not_scheduled;
return (
<Grid item xs={12} md={6} key={taskKey}>
<Box sx={{ p: 2, borderRadius: 2, border: `1px solid ${ui.border}`, bgcolor: 'rgba(15,23,42,0.5)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 600 }}>
{task.label || taskKey}
</Typography>
<Chip size="small" label={ui.label} sx={{ color: ui.color, bgcolor: ui.bg, border: `1px solid ${ui.border}` }} />
</Box>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.75)', display: 'block' }}>
Next: {task.next_execution ? new Date(task.next_execution).toLocaleString() : 'Not scheduled'}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.75)', display: 'block' }}>
Last success: {task.last_success ? new Date(task.last_success).toLocaleString() : 'No successful runs yet'}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.75)', display: 'block' }}>
Last failure: {task.last_failure ? new Date(task.last_failure).toLocaleString() : 'No failure recorded'}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.75)', display: 'block', mb: 1 }}>
Consecutive failures: {task.consecutive_failures ?? 0}
</Typography>
<Alert severity={status === 'active' ? 'success' : status === 'not_scheduled' ? 'info' : 'warning'} sx={{ bgcolor: 'rgba(15,23,42,0.65)', color: 'white', border: `1px solid ${ui.border}` }}>
{ui.action}
{task.latest_execution?.error_message ? ` Latest error: ${task.latest_execution.error_message}` : ''}
</Alert>
</Box>
</Grid>
);
})}
</Grid>
</Box>
</GlassCard>
</Box>
)}
{/* SEO Analyzer Panel */}
<SEOAnalyzerPanel
analysisData={analysisData}
onRunAnalysis={runSEOAnalysis}
loading={analysisLoading}
error={analysisError}
/>
{/* Copilot Suggestions Panel */}
<Box sx={{ mt: 4 }}>
<SEOCopilotSuggestions />
</Box>
{/* SEO Copilot Component for data loading and error handling */}
<SEOCopilot />
</>
)}
{/* Tab Content: Enterprise Analysis */}
{dashboardTab === 1 && (
<SEOAnalysisController />
)}
</motion.div>
</AnimatePresence>
</Container>
</DashboardContainer>
);
};
export default SEODashboard;