Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts

This commit is contained in:
ajaysi
2026-02-08 13:56:57 +05:30
parent 1db10ccd0f
commit e404a86502
333 changed files with 42223 additions and 10875 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Box,
Card,
@@ -15,63 +15,269 @@ import {
ListItem,
ListItemIcon,
ListItemText,
Alert
Alert,
Tooltip,
CircularProgress,
LinearProgress
} from '@mui/material';
import {
Search as SearchIcon,
Analytics as AnalyticsIcon,
CheckCircle as CheckIcon,
Insights as InsightsIcon
Check as CheckIcon,
Insights as InsightsIcon,
CheckCircleOutline as CheckCircleIcon,
AutoAwesome as AIIcon
} from '@mui/icons-material';
import { apiClient, longRunningApiClient } from '../../../api/client';
import { SitemapBenchmarkResults } from './SitemapBenchmarkResults';
import { StrategicInsightsResults } from './StrategicInsightsResults';
export const ComingSoonSection: React.FC = () => {
export const ComingSoonSection: React.FC<{ missingData?: boolean }> = ({ missingData = false }) => {
const [openModal, setOpenModal] = useState(false);
const [selectedFeature, setSelectedFeature] = useState<string | null>(null);
const [scheduledStatus, setScheduledStatus] = useState<any>(null);
const [sitemapBenchmarkRunning, setSitemapBenchmarkRunning] = useState(false);
const [sitemapBenchmarkError, setSitemapBenchmarkError] = useState<string | null>(null);
const [sitemapBenchmarkData, setSitemapBenchmarkData] = useState<any>(null);
const [loadingBenchmarkData, setLoadingBenchmarkData] = useState(false);
const [isLongRunning, setIsLongRunning] = useState(false);
const [strategicInsightsRunning, setStrategicInsightsRunning] = useState(false);
const [strategicInsightsError, setStrategicInsightsError] = useState<string | null>(null);
const [strategicInsightsData, setStrategicInsightsData] = useState<any>(null);
const [loadingStrategicHistory, setLoadingStrategicHistory] = useState(false);
useEffect(() => {
const loadStatus = async () => {
try {
const res = await apiClient.get('/api/onboarding/step3/scheduled-tasks-status');
setScheduledStatus(res.data);
// If report is available, fetch the full data
if (res.data?.competitive_sitemap_benchmarking?.report?.available) {
fetchBenchmarkData();
}
} catch {
setScheduledStatus(null);
}
};
const loadHistory = async () => {
setLoadingStrategicHistory(true);
try {
const res = await apiClient.get('/api/seo-dashboard/strategic-insights/history');
if (res.data?.history?.length > 0) {
setStrategicInsightsData(res.data.history[0]); // Show latest
}
} catch (e) {
console.error("Failed to fetch strategic insights history", e);
} finally {
setLoadingStrategicHistory(false);
}
};
loadStatus();
loadHistory();
}, []);
const fetchBenchmarkData = async () => {
setLoadingBenchmarkData(true);
try {
const res = await apiClient.get('/api/onboarding/step3/sitemap-benchmark-report');
setSitemapBenchmarkData(res.data);
} catch (e) {
console.error("Failed to fetch benchmark report", e);
} finally {
setLoadingBenchmarkData(false);
}
};
const deepStatus = scheduledStatus?.deep_competitor_analysis;
const deepBulb = deepStatus?.bulb || 'unknown';
const deepReason = deepStatus?.reason;
const deepTask = deepStatus?.task;
const sitemapStatus = scheduledStatus?.competitive_sitemap_benchmarking;
const sitemapBulb = sitemapStatus?.bulb || 'unknown';
const sitemapReason = sitemapStatus?.reason;
const sitemapReport = sitemapStatus?.report;
const getBulbColor = (bulb: string) => {
if (bulb === 'green') return '#22c55e';
if (bulb === 'red') return '#ef4444';
return '#94a3b8';
};
const getFeatureStatusLabel = (featureId: string, fallback: string) => {
if (featureId === 'sitemap-benchmarking') {
if (sitemapReport?.available) return 'Report Ready (No AI)';
return 'Available (No AI)';
}
return fallback;
};
const runSitemapBenchmark = async () => {
setSitemapBenchmarkError(null);
setSitemapBenchmarkRunning(true);
setIsLongRunning(false);
try {
await longRunningApiClient.post('/api/seo/competitive-sitemap-benchmarking/run', { max_competitors: 5 });
// Poll for completion with adaptive backoff
let attempts = 0;
const maxAttempts = 60; // Adjusted for ~10-12 mins (matching backend timeout)
let currentInterval = 2000;
const poll = async () => {
try {
attempts++;
// Mark as long running after ~2 minutes (approx 30 attempts)
if (attempts > 30) {
setIsLongRunning(true);
}
const res = await apiClient.get('/api/onboarding/step3/scheduled-tasks-status');
setScheduledStatus(res.data);
// Check status flag
const reportAvailable = res.data?.competitive_sitemap_benchmarking?.report?.available;
const reportStatus = res.data?.competitive_sitemap_benchmarking?.report?.status;
const reportError = res.data?.competitive_sitemap_benchmarking?.report?.error;
let reportFetched = false;
// Check for failure
if (reportStatus === 'failed' || reportError) {
setSitemapBenchmarkRunning(false);
setSitemapBenchmarkError(reportError || "Benchmark failed during execution.");
return; // Stop polling
}
// If available, try to fetch data
if (reportAvailable && !sitemapBenchmarkData) {
try {
const reportRes = await apiClient.get('/api/onboarding/step3/sitemap-benchmark-report');
if (reportRes?.data) {
setSitemapBenchmarkData(reportRes.data);
reportFetched = true;
}
} catch {
// Report might be saving or transient error
}
}
if (reportAvailable || reportFetched) {
if (!reportFetched && !sitemapBenchmarkData) {
await fetchBenchmarkData();
}
setOpenModal(false); // Close modal on success
setSitemapBenchmarkRunning(false);
setIsLongRunning(false);
// Focus on results
setTimeout(() => {
const element = document.getElementById('sitemap-benchmark-results');
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 500);
return; // Stop polling
} else if (attempts >= maxAttempts) {
setSitemapBenchmarkRunning(false);
setIsLongRunning(false);
setSitemapBenchmarkError("Benchmark timed out (10 mins limit). It may still be running in the background.");
return;
}
// Adaptive backoff: Slow down polling over time
if (attempts > 5) currentInterval = 4000; // After ~10s, slow to 4s
if (attempts > 15) currentInterval = 8000; // After ~50s, slow to 8s
if (attempts > 25) currentInterval = 15000; // After ~2m, slow to 15s
setTimeout(poll, currentInterval);
} catch (e) {
console.error("Polling error", e);
// Continue polling on error, but maybe wait longer
setTimeout(poll, currentInterval + 1000);
}
};
// Start polling
setTimeout(poll, currentInterval);
} catch (e: any) {
setSitemapBenchmarkError(e?.response?.data?.detail || e?.message || 'Failed to run benchmark');
setSitemapBenchmarkRunning(false);
}
};
const runStrategicInsights = async () => {
setStrategicInsightsError(null);
setStrategicInsightsRunning(true);
try {
const res = await apiClient.post('/api/seo-dashboard/strategic-insights/run');
if (res.data?.success) {
setStrategicInsightsData(res.data.report);
setOpenModal(false);
// Focus on results
setTimeout(() => {
const element = document.getElementById('strategic-insights-results');
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 500);
}
} catch (e: any) {
setStrategicInsightsError(e?.response?.data?.detail || e?.message || 'Failed to run strategic insights');
} finally {
setStrategicInsightsRunning(false);
}
};
const features = [
{
id: 'deep-competitor-analysis',
title: 'Deep Competitor Analysis',
description: 'Comprehensive analysis of competitor websites and content strategies',
description: 'We dig deep into your competitors\' strategies so you don\'t have to.',
icon: <SearchIcon />,
status: 'Coming Soon',
status: 'Auto-scheduled',
color: '#3b82f6',
details: [
'Analyze 15-25 relevant competitors automatically discovered',
'Crawl competitor homepages for content strategy analysis',
'Extract competitive advantages and market positioning',
'Identify content gaps and opportunities',
'Generate strategic recommendations based on competitive intelligence'
'Uncover their top-performing content and keywords',
'Identify their unique selling propositions (USPs)',
'Spot gaps in their content strategy you can exploit',
'Analyze their publishing frequency and patterns',
'Get a clear roadmap to outperform them'
]
},
{
id: 'sitemap-benchmarking',
title: 'Competitive Sitemap Benchmarking',
description: 'Compare your site structure against competitors',
description: 'See exactly how your website stacks up against the market leaders.',
icon: <AnalyticsIcon />,
status: 'In Development',
status: 'Available (No AI)',
color: '#10b981',
details: [
'Analyze competitor sitemaps for structure insights',
'Benchmark content volume against market leaders',
'Compare publishing frequency and patterns',
'Identify missing content categories',
'Get SEO structure optimization recommendations'
'Visualize your content volume vs. competitors',
'Compare site structure and ease of navigation',
'Check if you are publishing enough content',
'Find missing categories your competitors have',
'Get instant, data-backed improvement ideas'
]
},
{
id: 'ai-competitive-insights',
title: 'AI-Powered Competitive Insights',
description: 'Advanced AI analysis of competitive landscape',
description: 'Turn raw data into a winning game plan with AI.',
icon: <InsightsIcon />,
status: 'Planned',
color: '#8b5cf6',
details: [
'AI-generated competitive intelligence reports',
'Market positioning analysis with business impact',
'Content strategy recommendations based on competitor data',
'Competitive advantage identification',
'Strategic roadmap for competitive differentiation'
'Receive a personalized "Winning Moves" report',
'Understand the business impact of your strategy',
'Get specific content ideas to steal market share',
'Identify your true competitive advantages',
'Build a roadmap for long-term growth'
]
}
];
@@ -87,10 +293,10 @@ export const ComingSoonSection: React.FC = () => {
<>
<Box sx={{ mt: 4, mb: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, color: '#1e293b', mb: 1.5 }}>
🔍 Coming Soon
🔍 Scheduled Tasks
</Typography>
<Typography variant="body1" sx={{ color: '#64748b', mb: 4, fontSize: '1.1rem' }}>
Advanced competitor analysis features to give you the competitive edge
Long-running analyses that run automatically after onboarding
</Typography>
<Grid container spacing={2}>
@@ -134,18 +340,48 @@ export const ComingSoonSection: React.FC = () => {
{feature.icon}
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 700, color: '#1e293b', mb: 1 }}>
{feature.title}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 700, color: '#1e293b' }}>
{feature.title}
</Typography>
{feature.id === 'deep-competitor-analysis' && (
<Tooltip title={deepReason || 'Scheduled automatically after onboarding completion'}>
<Box
sx={{
width: 10,
height: 10,
borderRadius: '50%',
bgcolor: getBulbColor(deepBulb),
boxShadow: `0 0 0 4px ${getBulbColor(deepBulb)}20`
}}
/>
</Tooltip>
)}
{feature.id === 'sitemap-benchmarking' && (
<Tooltip title={sitemapReport?.available ? `Last run: ${sitemapReport?.last_run || 'available'}` : (sitemapReason || 'Run anytime (No AI)')}>
<Box
sx={{
width: 10,
height: 10,
borderRadius: '50%',
bgcolor: getBulbColor(sitemapBulb),
boxShadow: `0 0 0 4px ${getBulbColor(sitemapBulb)}20`
}}
/>
</Tooltip>
)}
</Box>
<Chip
label={feature.status}
label={getFeatureStatusLabel(feature.id, feature.status)}
size="small"
icon={feature.status === 'Auto-scheduled' ? <CheckCircleIcon sx={{ '&&': { color: feature.color, fontSize: '1rem' } }} /> : undefined}
sx={{
backgroundColor: `${feature.color}20`,
color: feature.color,
fontWeight: 600,
backgroundColor: feature.status === 'Auto-scheduled' ? '#ecfdf5' : `${feature.color}20`,
color: feature.status === 'Auto-scheduled' ? '#059669' : feature.color,
fontWeight: 700,
fontSize: '0.75rem',
height: 24,
border: feature.status === 'Auto-scheduled' ? '1px solid #a7f3d0' : 'none',
'& .MuiChip-label': {
px: 1.5
}
@@ -204,10 +440,36 @@ export const ComingSoonSection: React.FC = () => {
</Alert>
</Box>
{sitemapReport?.available && sitemapBenchmarkData && (
<Box id="sitemap-benchmark-results" sx={{ mt: 4, animation: 'fadeIn 0.5s ease-out' }}>
<SitemapBenchmarkResults
data={{
user: sitemapBenchmarkData.user,
competitors: sitemapBenchmarkData.competitors,
timestamp: sitemapBenchmarkData.timestamp,
benchmark: sitemapBenchmarkData.benchmark || {}
}}
/>
</Box>
)}
{strategicInsightsData && (
<Box id="strategic-insights-results" sx={{ mt: 4 }}>
<StrategicInsightsResults report={strategicInsightsData} />
</Box>
)}
{/* Feature Details Modal */}
<Dialog
open={openModal}
onClose={() => setOpenModal(false)}
onClose={(event, reason) => {
if (reason !== 'backdropClick' && reason !== 'escapeKeyDown') {
setOpenModal(false);
} else if (!sitemapBenchmarkRunning) {
setOpenModal(false);
}
}}
disableEscapeKeyDown={sitemapBenchmarkRunning}
maxWidth="md"
fullWidth
PaperProps={{
@@ -292,69 +554,173 @@ export const ComingSoonSection: React.FC = () => {
How It Works:
</Typography>
<Typography variant="body1" sx={{ color: '#64748b', fontSize: '1.05rem', lineHeight: 1.7 }}>
Our AI automatically discovers 15-25 relevant competitors using advanced search algorithms.
Then we crawl each competitor's homepage to analyze their content strategy, identify their
competitive advantages, and find content gaps that present opportunities for your business.
Once you finish onboarding, Alwrity automatically starts analyzing the competitors we found.
We compare your website's performance against theirs to find hidden opportunities.
You'll see the results in your SEO Dashboard, including a breakdown of what makes them successful and how you can do better.
</Typography>
<Box sx={{ mt: 2 }}>
<Typography variant="body2" sx={{ color: '#334155', fontWeight: 600 }}>
Status:
</Typography>
<Typography variant="body2" sx={{ color: '#64748b', mt: 0.5 }}>
{deepBulb === 'red'
? (deepReason || "Not eligible yet. No competitors found.")
: "Eligible. This will run automatically after onboarding."}
</Typography>
{deepTask?.exists && (
<Typography variant="body2" sx={{ color: '#64748b', mt: 0.5 }}>
{deepTask.last_status
? `Last run: ${deepTask.last_status}${deepTask.last_run ? ` at ${deepTask.last_run}` : ''}`
: (deepTask.next_execution ? `Next scheduled: ${deepTask.next_execution}` : `Task status: ${deepTask.status || 'unknown'}`)}
</Typography>
)}
</Box>
</Box>
)}
{selectedFeatureData.id === 'sitemap-benchmarking' && (
<Box sx={{ mt: 3, p: 3, backgroundColor: '#f0f9ff', borderRadius: 3, border: '1px solid #e2e8f0' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 2, color: '#1e293b', fontSize: '1.1rem' }}>
Competitive Intelligence:
Why This Matters:
</Typography>
<Typography variant="body1" sx={{ color: '#64748b', fontSize: '1.05rem', lineHeight: 1.7 }}>
We analyze competitor sitemaps to understand their content structure, publishing patterns,
and SEO optimization. This gives you data-driven insights into how your site compares to
market leaders and what improvements will have the biggest competitive impact.
We scan competitor websites to understand how they organize their content and how often they publish.
This shows you exactly where you need to improve to match or beat the market leaders.
</Typography>
{sitemapBenchmarkRunning && (
<Box sx={{ mt: 3, mb: 2 }}>
<LinearProgress />
<Typography variant="caption" sx={{ mt: 1, display: 'block', textAlign: 'center', color: '#64748b' }}>
{isLongRunning
? "This is taking longer than usual. Large websites can take a few minutes..."
: "Analyzing competitor websites... please wait."}
</Typography>
</Box>
)}
{loadingBenchmarkData ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
<CircularProgress />
</Box>
) : sitemapReport?.available ? (
<Box sx={{ mt: 2 }}>
<Alert severity="success">
Benchmark Report is ready! Close this window to view the detailed analysis below.
</Alert>
</Box>
) : (
<Box sx={{ mt: 2 }}>
<Typography variant="body2" sx={{ color: '#334155', fontWeight: 700 }}>
Status:
</Typography>
<Typography variant="body2" sx={{ color: sitemapReport?.status === 'failed' ? '#ef4444' : '#64748b', mt: 0.5 }}>
{sitemapReport?.available
? 'Report is ready.'
: sitemapReport?.status === 'failed'
? `Failed: ${sitemapReport?.error || 'Unknown error'}`
: sitemapReport?.status === 'processing'
? 'Analysis in progress...'
: 'No report yet. You can run it now (No AI).'}
</Typography>
{sitemapReport?.last_run && (
<Typography variant="caption" sx={{ display: 'block', mt: 0.75, color: '#64748b' }}>
Last run: {sitemapReport.last_run}
</Typography>
)}
</Box>
)}
{sitemapBenchmarkError && (
<Typography variant="caption" sx={{ display: 'block', mt: 0.75, color: '#ef4444' }}>
{sitemapBenchmarkError}
</Typography>
)}
</Box>
)}
{selectedFeatureData.id === 'ai-competitive-insights' && (
<Box sx={{ mt: 3, p: 3, backgroundColor: '#f0f9ff', borderRadius: 3, border: '1px solid #e2e8f0' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 2, color: '#1e293b', fontSize: '1.1rem' }}>
Strategic Value:
The "Winning Moves" Advantage:
</Typography>
<Typography variant="body1" sx={{ color: '#64748b', fontSize: '1.05rem', lineHeight: 1.7 }}>
Our AI analyzes the competitive landscape to provide strategic recommendations with
business impact estimates. You'll get specific content priorities, competitive positioning
advice, and a roadmap for differentiating your brand in the market.
We turn millions of data points into a clear "Winning Moves" report.
See exactly which content will drive the most traffic and revenue,
and get a step-by-step plan to steal market share from your competitors.
</Typography>
{strategicInsightsRunning && (
<Box sx={{ mt: 3, mb: 2 }}>
<LinearProgress />
<Typography variant="caption" sx={{ mt: 1, display: 'block', textAlign: 'center', color: '#64748b' }}>
Our AI is analyzing market shifts and competitor moves... this takes about 30-45 seconds.
</Typography>
</Box>
)}
{strategicInsightsError && (
<Typography variant="caption" sx={{ display: 'block', mt: 1.5, color: '#ef4444', textAlign: 'center' }}>
{strategicInsightsError}
</Typography>
)}
</Box>
)}
</>
)}
</DialogContent>
<DialogActions sx={{ p: 3, pt: 1, backgroundColor: '#f8fafc', borderTop: '1px solid #e2e8f0' }}>
<Button
onClick={() => setOpenModal(false)}
variant="outlined"
sx={{
borderColor: '#d1d5db',
color: '#6b7280',
'&:hover': {
borderColor: '#9ca3af',
backgroundColor: '#f9fafb'
}
}}
>
Close
</Button>
<DialogActions sx={{ p: 3, pt: 1, backgroundColor: '#f8fafc', borderTop: '1px solid #e2e8f0', justifyContent: 'space-between' }}>
{selectedFeatureData?.id === 'sitemap-benchmarking' && (
<Button
onClick={runSitemapBenchmark}
variant="contained"
disabled={sitemapBenchmarkRunning}
startIcon={sitemapBenchmarkRunning ? <CircularProgress size={20} color="inherit" /> : null}
sx={{
backgroundColor: '#10b981',
'&:hover': { backgroundColor: '#059669' },
textTransform: 'none',
fontWeight: 700
}}
>
{sitemapBenchmarkRunning ? 'Running Benchmark...' : 'Run Benchmark Now (No AI)'}
</Button>
)}
{selectedFeatureData?.id === 'ai-competitive-insights' && (
<Button
onClick={runStrategicInsights}
variant="contained"
disabled={strategicInsightsRunning || missingData}
startIcon={strategicInsightsRunning ? <CircularProgress size={20} color="inherit" /> : <AIIcon />}
sx={{
backgroundColor: '#8b5cf6',
'&:hover': { backgroundColor: '#7c3aed' },
textTransform: 'none',
fontWeight: 700
}}
>
{strategicInsightsRunning ? 'Generating Winning Moves...' : (missingData ? 'Complete Step 2 First' : 'Run AI Analysis Now')}
</Button>
)}
<Button
onClick={() => setOpenModal(false)}
variant="contained"
sx={{
backgroundColor: selectedFeatureData?.color || '#3b82f6',
backgroundColor: '#3b82f6',
px: 4,
py: 1,
borderRadius: 2,
textTransform: 'none',
fontWeight: 600,
boxShadow: '0 4px 6px -1px rgba(59, 130, 246, 0.5)',
'&:hover': {
backgroundColor: selectedFeatureData?.color || '#3b82f6',
opacity: 0.9
backgroundColor: '#2563eb',
boxShadow: '0 10px 15px -3px rgba(59, 130, 246, 0.5)',
}
}}
>
Notify Me When Ready
Got it, thanks!
</Button>
</DialogActions>
</Dialog>

View File

@@ -0,0 +1,657 @@
import React from 'react';
import {
Box,
Typography,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Grid,
Chip,
Card,
CardContent,
Tooltip,
useTheme,
Alert,
List,
ListItem,
ListItemText
} from '@mui/material';
import {
Speed as SpeedIcon,
Description as DescriptionIcon,
AccountTree as TreeIcon,
TrendingUp as TrendingUpIcon,
Lightbulb as LightbulbIcon,
CheckCircle as CheckIcon
} from '@mui/icons-material';
export interface BenchmarkMetrics {
total_urls: number;
publishing_velocity: number;
average_path_depth: number;
max_path_depth: number;
top_url_patterns: Record<string, number>;
file_types: Record<string, number>;
date_range?: {
start: string;
end: string;
};
}
export interface Opportunity {
type: string;
title: string;
items?: Array<{
section: string;
competitor_presence: number;
competitor_url_count: number;
}>;
metrics?: Record<string, number>;
}
export interface BenchmarkData {
user?: {
summary: BenchmarkMetrics;
error?: string;
};
competitors?: {
summaries: Record<string, BenchmarkMetrics>;
errors?: Record<string, string>;
};
// Support for potential flat structure (legacy or different service versions)
user_summary?: BenchmarkMetrics;
competitor_summaries?: Record<string, BenchmarkMetrics>;
timestamp?: string;
benchmark?: {
website_url?: string;
competitors_analyzed?: number;
user_sections_count?: number;
competitor_section_leaders?: Array<{
competitor_url: string;
total_urls: number;
sections_count: number;
publishing_velocity: number;
average_path_depth?: number;
lastmod_coverage?: number;
}>;
gaps?: {
missing_sections?: Array<{
section: string;
competitor_presence: number;
competitor_url_count: number;
}>;
};
opportunities?: Array<Opportunity>;
gaps_vs_competitors?: {
missing_sections?: Array<{
section: string;
competitor_url_count: number;
competitor_presence?: number;
}>;
};
keyword_hints?: Array<{
keyword: string;
seen_in_url_patterns: boolean;
}>;
};
}
export interface Props {
data: BenchmarkData;
}
export const SitemapBenchmarkResults: React.FC<Props> = ({ data }) => {
const theme = useTheme();
const { benchmark } = data;
// Handle data mapping from potentially nested structure
const user_summary = data.user_summary || data.user?.summary || {} as BenchmarkMetrics;
const competitor_summaries = data.competitor_summaries || data.competitors?.summaries || {};
const competitor_errors = data.competitors?.errors || {};
const user_error = data.user?.error;
// Calculate competitor averages
const competitorUrls = Object.keys(competitor_summaries || {});
const avgCompetitorUrls = competitorUrls.length > 0
? Math.round(competitorUrls.reduce((acc, url) => acc + (competitor_summaries[url]?.total_urls || 0), 0) / competitorUrls.length)
: 0;
const avgCompetitorVelocity = competitorUrls.length > 0
? parseFloat((competitorUrls.reduce((acc, url) => acc + (competitor_summaries[url]?.publishing_velocity || 0), 0) / competitorUrls.length).toFixed(2))
: 0;
const avgCompetitorDepth = competitorUrls.length > 0
? parseFloat((competitorUrls.reduce((acc, url) => acc + (competitor_summaries[url]?.average_path_depth || 0), 0) / competitorUrls.length).toFixed(2))
: 0;
const MetricCard = ({ title, userValue, competitorValue, icon, unit = '', description }: any) => {
const isBelowAvg = userValue < competitorValue;
return (
<Card
elevation={0}
sx={{
height: '100%',
border: `1px solid #e2e8f0`,
borderRadius: 3,
bgcolor: '#f8fafc',
transition: 'all 0.2s ease',
'&:hover': {
borderColor: theme.palette.primary.main,
bgcolor: '#ffffff',
boxShadow: '0 4px 12px rgba(0,0,0,0.05)'
}
}}
>
<CardContent sx={{ p: 2.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ p: 1, borderRadius: 2, bgcolor: theme.palette.primary.main + '10', color: theme.palette.primary.main, mr: 1.5, display: 'flex' }}>
{icon}
</Box>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#475569' }}>
{title}
</Typography>
</Box>
<Tooltip title={description} arrow placement="top">
<Box sx={{ cursor: 'help', color: '#94a3b8', display: 'flex' }}>
<LightbulbIcon sx={{ fontSize: 18 }} />
</Box>
</Tooltip>
</Box>
<Box sx={{ mb: 2.5 }}>
<Typography variant="h4" sx={{ fontWeight: 800, color: '#1e293b', mb: 0.5 }}>
{userValue}{unit}
</Typography>
<Typography variant="caption" sx={{ color: '#64748b', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px' }}>
Your Site
</Typography>
</Box>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
pt: 2,
borderTop: `1px dashed #e2e8f0`
}}>
<Box>
<Typography variant="body2" sx={{ fontWeight: 700, color: '#334155' }}>
{competitorValue}{unit}
</Typography>
<Typography variant="caption" sx={{ color: '#94a3b8' }}>
Competitor Avg
</Typography>
</Box>
<Chip
label={userValue >= competitorValue ? 'Above Avg' : 'Below Avg'}
size="small"
sx={{
height: 24,
fontSize: '0.7rem',
fontWeight: 700,
bgcolor: userValue >= competitorValue ? '#ecfdf5' : '#fff7ed',
color: userValue >= competitorValue ? '#059669' : '#c2410c',
border: `1px solid ${userValue >= competitorValue ? '#a7f3d0' : '#ffedd5'}`,
borderRadius: 1.5
}}
/>
</Box>
</CardContent>
</Card>
);
};
const metricDescriptions = {
volume: "Total number of indexed pages discovered in your sitemap compared to the average of your top competitors.",
velocity: "Average number of new pages published per week, indicating how active the content strategy is.",
depth: "Average number of clicks required to reach content from the homepage based on URL structure."
};
return (
<Box sx={{ mt: 4 }}>
<Box sx={{ mb: 4 }}>
<Typography variant="h5" sx={{ fontWeight: 800, color: '#1e293b', mb: 1, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<TrendingUpIcon sx={{ color: theme.palette.primary.main, fontSize: 28 }} />
Benchmark Analysis
</Typography>
<Typography variant="body1" sx={{ color: '#64748b' }}>
Comparison based on <strong>{competitorUrls.length}</strong> competitor sitemaps.
</Typography>
</Box>
{/* Main Metrics Row with Errors */}
<Grid container spacing={3} sx={{ mb: 5 }}>
{/* Errors Area A (if exists) */}
{(user_error || Object.keys(competitor_errors).length > 0) ? (
<Grid item xs={12}>
<Grid container spacing={3}>
<Grid item xs={12} lg={4}>
<Box sx={{ height: '100%' }}>
{user_error && (
<Alert severity="error" sx={{ mb: 2, borderRadius: 3, border: '1px solid #fee2e2' }}>
<Typography variant="subtitle2" fontWeight="bold">User Sitemap Error:</Typography>
{user_error}
</Alert>
)}
{Object.keys(competitor_errors).length > 0 && (
<Alert
severity="warning"
sx={{
height: '100%',
borderRadius: 3,
border: '1px solid #ffedd5',
bgcolor: '#fffaf5',
'& .MuiAlert-message': { width: '100%' }
}}
>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#9a3412' }}>
{Object.keys(competitor_errors).length} competitors could not be analyzed:
</Typography>
<Box sx={{ maxHeight: 150, overflowY: 'auto', pr: 1 }}>
<List dense disablePadding>
{Object.entries(competitor_errors).map(([url, error]) => (
<ListItem key={url} disablePadding sx={{ py: 0.5 }}>
<ListItemText
primary={url.replace('https://', '').replace('www.', '')}
secondary={String(error)}
primaryTypographyProps={{ variant: 'caption', fontWeight: 700, color: '#c2410c' }}
secondaryTypographyProps={{ variant: 'caption', color: '#9a3412' }}
sx={{ m: 0 }}
/>
</ListItem>
))}
</List>
</Box>
</Alert>
)}
</Box>
</Grid>
<Grid item xs={12} lg={8}>
<Grid container spacing={2}>
<Grid item xs={12} sm={4}>
<MetricCard
title="Content Volume"
userValue={user_summary.total_urls || 0}
competitorValue={avgCompetitorUrls}
icon={<DescriptionIcon />}
description={metricDescriptions.volume}
/>
</Grid>
<Grid item xs={12} sm={4}>
<MetricCard
title="Publishing Velocity"
userValue={user_summary.publishing_velocity ? parseFloat(user_summary.publishing_velocity.toFixed(2)) : 0}
competitorValue={avgCompetitorVelocity}
icon={<SpeedIcon />}
unit=" /wk"
description={metricDescriptions.velocity}
/>
</Grid>
<Grid item xs={12} sm={4}>
<MetricCard
title="Structure Depth"
userValue={user_summary.average_path_depth ? parseFloat(user_summary.average_path_depth.toFixed(2)) : 0}
competitorValue={avgCompetitorDepth}
icon={<TreeIcon />}
unit=" clicks"
description={metricDescriptions.depth}
/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
) : (
<>
<Grid item xs={12} md={4}>
<MetricCard
title="Content Volume"
userValue={user_summary.total_urls || 0}
competitorValue={avgCompetitorUrls}
icon={<DescriptionIcon />}
description={metricDescriptions.volume}
/>
</Grid>
<Grid item xs={12} md={4}>
<MetricCard
title="Publishing Velocity"
userValue={user_summary.publishing_velocity ? parseFloat(user_summary.publishing_velocity.toFixed(2)) : 0}
competitorValue={avgCompetitorVelocity}
icon={<SpeedIcon />}
unit=" /wk"
description={metricDescriptions.velocity}
/>
</Grid>
<Grid item xs={12} md={4}>
<MetricCard
title="Structure Depth"
userValue={user_summary.average_path_depth ? parseFloat(user_summary.average_path_depth.toFixed(2)) : 0}
competitorValue={avgCompetitorDepth}
icon={<TreeIcon />}
unit=" clicks"
description={metricDescriptions.depth}
/>
</Grid>
</>
)}
</Grid>
{/* Industry Benchmarks Section */}
<Box sx={{ mb: 6 }}>
<Typography variant="h6" sx={{ fontWeight: 800, color: '#1e293b', mb: 3, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<LightbulbIcon sx={{ color: '#f59e0b' }} />
Industry Benchmarks
</Typography>
<Paper
elevation={0}
sx={{
p: 4,
borderRadius: 4,
border: '1px solid #e2e8f0',
bgcolor: '#ffffff'
}}
>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 3, color: '#334155' }}>
Common competitor sections you may be missing
</Typography>
{(benchmark?.gaps?.missing_sections || benchmark?.gaps_vs_competitors?.missing_sections) &&
(benchmark?.gaps?.missing_sections || benchmark?.gaps_vs_competitors?.missing_sections || []).length > 0 ? (
<Grid container spacing={2}>
{(benchmark?.gaps?.missing_sections || benchmark?.gaps_vs_competitors?.missing_sections || []).map((gap: any, index: number) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<Box
sx={{
p: 2,
borderRadius: 2,
bgcolor: '#f1f5f9',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
border: '1px solid #e2e8f0'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<CheckIcon sx={{ color: '#94a3b8', fontSize: 18 }} />
<Typography variant="body2" sx={{ fontWeight: 700, color: '#475569', textTransform: 'capitalize' }}>
/{gap.section}
</Typography>
</Box>
<Tooltip title={`${gap.competitor_count || Math.round((gap.competitor_presence || 0) * competitorUrls.length)} out of ${competitorUrls.length} competitors have this section.`}>
<Chip
label={`${Math.round((gap.competitor_presence || 0) * 100)}% Presence`}
size="small"
sx={{ height: 20, fontSize: '0.65rem', fontWeight: 800, bgcolor: '#e2e8f0', color: '#64748b' }}
/>
</Tooltip>
</Box>
</Grid>
))}
</Grid>
) : (
<Box sx={{ p: 4, textAlign: 'center', bgcolor: '#f8fafc', borderRadius: 3, border: '1px dashed #e2e8f0' }}>
<Typography variant="body2" sx={{ color: '#94a3b8', fontWeight: 500 }}>
No significant content gaps identified compared to your competitors.
</Typography>
</Box>
)}
</Paper>
</Box>
{/* Actionable Insights */}
{benchmark?.opportunities && benchmark.opportunities.length > 0 && (
<Box sx={{ mb: 6 }}>
<Typography variant="h6" sx={{ fontWeight: 800, color: '#1e293b', mb: 3, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<LightbulbIcon sx={{ color: '#f59e0b' }} />
Strategic Insights
</Typography>
<Grid container spacing={2}>
{benchmark.opportunities.map((opp: any, index: number) => (
<Grid item xs={12} md={6} key={index}>
<Paper
elevation={0}
sx={{
p: 3,
height: '100%',
bgcolor: '#ffffff',
border: '1px solid #e2e8f0',
borderRadius: 3,
display: 'flex',
flexDirection: 'column'
}}
>
<Box sx={{ display: 'flex', gap: 2, mb: 1.5 }}>
<Box sx={{
width: 24,
height: 24,
borderRadius: '50%',
bgcolor: theme.palette.primary.main + '20',
color: theme.palette.primary.main,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
mt: 0.2
}}>
<Typography variant="caption" sx={{ fontWeight: 900 }}>!</Typography>
</Box>
<Typography variant="body1" sx={{ fontWeight: 700, color: '#334155', lineHeight: 1.4 }}>
{opp.title}
</Typography>
</Box>
{opp.metrics && (
<Box sx={{ ml: 5, mt: 'auto', pt: 2, borderTop: '1px solid #f1f5f9', display: 'flex', gap: 3, flexWrap: 'wrap' }}>
{Object.entries(opp.metrics).map(([key, value]) => (
<Box key={key} sx={{ mb: 1, '&:last-child': { mb: 0 } }}>
<Typography variant="caption" sx={{ color: '#64748b', textTransform: 'uppercase', letterSpacing: 0.5 }}>
{(() => {
if (key.startsWith('user_')) {
return 'Your ' + key.replace('user_', '').replace(/_/g, ' ');
}
if (key.includes('competitor_median_')) {
return 'Competitor Avg ' + key.replace('competitor_median_', '').replace(/_/g, ' ');
}
return key.replace(/_/g, ' ');
})()}
</Typography>
<Typography variant="body2" sx={{ fontWeight: 700, color: '#475569' }}>
{typeof value === 'number' ? value.toFixed(2) : String(value)}
</Typography>
</Box>
))}
</Box>
)}
</Paper>
</Grid>
))}
</Grid>
</Box>
)}
{/* Competitor Leaders Table */}
{benchmark?.competitor_section_leaders && benchmark.competitor_section_leaders.length > 0 && (
<Box sx={{ mb: 4 }}>
<Typography variant="subtitle1" fontWeight={700} gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2, color: '#1e293b' }}>
<DescriptionIcon color="info" fontSize="small" />
Top Competitor Stats
</Typography>
<TableContainer component={Paper} elevation={0} sx={{ border: '1px solid #e2e8f0', borderRadius: 2 }}>
<Table size="small">
<TableHead>
<TableRow sx={{ bgcolor: '#f8fafc' }}>
<TableCell sx={{ fontWeight: 700, color: '#1e293b' }}>Competitor</TableCell>
<Tooltip title="Total number of pages found in the sitemap" arrow>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b', cursor: 'help' }}>Total URLs</TableCell>
</Tooltip>
<Tooltip title="Number of distinct URL path sections (e.g., /blog/, /products/)" arrow>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b', cursor: 'help' }}>Sections</TableCell>
</Tooltip>
<Tooltip title="Average new pages published per week" arrow>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b', cursor: 'help' }}>Velocity/wk</TableCell>
</Tooltip>
<Tooltip title="Average URL path depth (clicks from root)" arrow>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b', cursor: 'help' }}>Avg Depth</TableCell>
</Tooltip>
<Tooltip title="Percentage of URLs with valid last-modified dates" arrow>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b', cursor: 'help' }}>Date Coverage</TableCell>
</Tooltip>
</TableRow>
</TableHead>
<TableBody>
{benchmark.competitor_section_leaders.map((comp, idx) => (
<TableRow key={idx} sx={{ bgcolor: '#ffffff', '&:last-child td, &:last-child th': { border: 0 } }}>
<TableCell component="th" scope="row" sx={{ maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 500, color: '#334155' }}>
<Tooltip title={comp.competitor_url}>
<span>{new URL(comp.competitor_url).hostname}</span>
</Tooltip>
</TableCell>
<TableCell align="right" sx={{ color: '#475569' }}>{comp.total_urls}</TableCell>
<TableCell align="right" sx={{ color: '#475569' }}>{comp.sections_count}</TableCell>
<TableCell align="right" sx={{ color: '#475569' }}>{comp.publishing_velocity?.toFixed(1) || '-'}</TableCell>
<TableCell align="right" sx={{ color: '#475569' }}>{comp.average_path_depth?.toFixed(1) || '-'}</TableCell>
<TableCell align="right" sx={{ color: '#475569' }}>
{comp.lastmod_coverage ? `${(comp.lastmod_coverage * 100).toFixed(0)}%` : '-'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
)}
{/* Competitor Content Strategy Patterns (formerly Content Gaps) */}
{((benchmark?.gaps_vs_competitors?.missing_sections && benchmark.gaps_vs_competitors.missing_sections.length > 0) ||
(benchmark?.gaps?.missing_sections && benchmark.gaps.missing_sections.length > 0)) && (
<Box sx={{ mb: 4 }}>
<Typography variant="subtitle1" fontWeight={700} gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1, color: '#0f172a' }}>
<TrendingUpIcon color="primary" fontSize="small" />
Competitor Content Strategy Patterns
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3, maxWidth: '800px' }}>
The following content categories appear frequently across your competitors' websites but are missing from yours.
Consider creating content in these areas to capture similar traffic and improve your competitive positioning.
</Typography>
<Grid container spacing={2}>
{(benchmark?.gaps?.missing_sections || benchmark?.gaps_vs_competitors?.missing_sections || [])
.filter((gap: any) => gap.section && gap.section.length > 3) // Filter out short sections like language codes (/es, /fr)
.map((gap: any, index: number) => {
// Fix for "200% Presence" bug: normalize values
let presence = gap.competitor_presence || 0;
// If presence > 1, it's likely a raw count, so normalize it
if (presence > 1) {
presence = presence / (competitorUrls.length || 1);
}
const percentage = Math.min(Math.round(presence * 100), 100);
const count = gap.competitor_count || Math.round(presence * competitorUrls.length);
return (
<Grid item xs={12} sm={6} md={4} key={index}>
<Paper variant="outlined" sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', bgcolor: '#ffffff', border: '1px solid #e2e8f0' }}>
<Box>
<Typography variant="subtitle2" fontWeight="bold" sx={{ color: '#1e293b' }}>
{gap.section}
</Typography>
<Typography variant="caption" sx={{ color: '#64748b' }}>
Used by {count} of {competitorUrls.length} competitors
</Typography>
</Box>
<Tooltip title={`${percentage}% of your competitors have this section`}>
<Chip
label={`${percentage}% Match`}
size="small"
color="primary"
variant="outlined"
sx={{ fontWeight: 700, bgcolor: '#eff6ff', border: '1px solid #bfdbfe', color: '#1d4ed8' }}
/>
</Tooltip>
</Paper>
</Grid>
);
})}
</Grid>
</Box>
)}
{benchmark?.keyword_hints && benchmark.keyword_hints.length > 0 && (
<Box sx={{ mb: 4 }}>
<Typography variant="subtitle1" fontWeight={700} gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<LightbulbIcon color="warning" fontSize="small" />
Keyword Opportunities (from URL patterns)
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{benchmark.keyword_hints.map((hint, index) => (
<Chip
key={index}
label={hint.keyword}
color={hint.seen_in_url_patterns ? "success" : "default"}
variant={hint.seen_in_url_patterns ? "filled" : "outlined"}
icon={hint.seen_in_url_patterns ? <CheckIcon fontSize="small" /> : undefined}
sx={{ borderColor: theme.palette.divider }}
/>
))}
</Box>
</Box>
)}
<TableContainer component={Paper} elevation={0} sx={{ border: '1px solid #e2e8f0', borderRadius: 2 }}>
<Table size="small">
<TableHead sx={{ bgcolor: '#f8fafc' }}>
<TableRow>
<TableCell sx={{ fontWeight: 700, color: '#1e293b' }}>Website</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b' }}>Total Pages</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b' }}>Velocity (posts/week)</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b' }}>Avg Depth</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#1e293b' }}>Top Category</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* User Row */}
<TableRow sx={{ bgcolor: theme.palette.primary.main + '08', '& td, & th': { borderBottom: '1px solid #e2e8f0' } }}>
<TableCell component="th" scope="row">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="body2" fontWeight={700} color="primary.main">Your Website</Typography>
<Chip label="You" size="small" color="primary" sx={{ height: 20, fontSize: '0.65rem', fontWeight: 700 }} />
</Box>
</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#334155' }}>{user_summary.total_urls}</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#334155' }}>{user_summary.publishing_velocity?.toFixed(2) || '0.00'}</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#334155' }}>{user_summary.average_path_depth?.toFixed(2) || '0.00'}</TableCell>
<TableCell align="right" sx={{ fontWeight: 700, color: '#334155' }}>
{Object.keys(user_summary.top_url_patterns || {})[0] || '-'}
</TableCell>
</TableRow>
{/* Competitor Rows */}
{competitorUrls.map((url, idx) => {
const data = competitor_summaries[url];
const domain = url.replace(/^https?:\/\/(www\.)?/, '').split('/')[0];
return (
<TableRow key={url} sx={{ bgcolor: '#ffffff', '&:last-child td, &:last-child th': { border: 0 } }}>
<TableCell component="th" scope="row" sx={{ color: '#475569', fontWeight: 500 }}>
{domain}
</TableCell>
<TableCell align="right" sx={{ color: '#64748b' }}>{data?.total_urls || 0}</TableCell>
<TableCell align="right" sx={{ color: '#64748b' }}>{data?.publishing_velocity?.toFixed(2) || '0.00'}</TableCell>
<TableCell align="right" sx={{ color: '#64748b' }}>{data?.average_path_depth?.toFixed(2) || '0.00'}</TableCell>
<TableCell align="right" sx={{ color: '#64748b' }}>
{Object.keys(data?.top_url_patterns || {})[0] || '-'}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Box>
);
};

View File

@@ -0,0 +1,265 @@
import React from 'react';
import {
Box,
Typography,
Paper,
Grid,
Chip,
Card,
CardContent,
Tooltip,
useTheme,
Button,
List,
ListItem,
ListItemIcon,
ListItemText,
Divider,
Avatar
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
Lightbulb as LightbulbIcon,
Warning as WarningIcon,
Speed as SpeedIcon,
CheckCircle as CheckIcon,
ArrowForward as ArrowIcon,
Star as StarIcon,
Bolt as BoltIcon,
AutoAwesome as AIIcon
} from '@mui/icons-material';
export interface StrategicInsight {
type: string;
insight: string;
reasoning?: string;
priority: string;
estimated_impact: string;
implementation_time?: string;
}
export interface ContentRecommendation {
recommendation: string;
priority: string;
estimated_traffic: string;
roi_estimate: string;
}
export interface StrategicInsightsReport {
week_commencing: string;
generated_at: string;
metrics: {
market_velocity: number;
user_velocity: number;
};
insights: {
the_big_move: StrategicInsight;
low_hanging_fruit: ContentRecommendation[];
threat_alerts: StrategicInsight[];
};
raw_data?: any;
}
export interface Props {
report: StrategicInsightsReport;
hideCreateContent?: boolean;
}
export const StrategicInsightsResults: React.FC<Props> = ({ report, hideCreateContent = false }) => {
const theme = useTheme();
const { insights, metrics, week_commencing } = report;
const handleCreateContent = (topic: string) => {
// Logic to redirect to Blog Writer with pre-filled prompt
const prompt = encodeURIComponent(`Write a high-quality blog post about "${topic}" that outperforms my competitors. Focus on unique value propositions and clear CTAs.`);
window.location.href = `/blog-writer?prompt=${prompt}`;
};
const PriorityChip = ({ priority }: { priority: string }) => {
const isHigh = priority?.toLowerCase() === 'high';
return (
<Chip
label={priority}
size="small"
sx={{
height: 20,
fontSize: '0.65rem',
fontWeight: 800,
bgcolor: isHigh ? '#fee2e2' : '#f1f5f9',
color: isHigh ? '#b91c1c' : '#475569',
border: `1px solid ${isHigh ? '#fecaca' : '#e2e8f0'}`,
ml: 1
}}
/>
);
};
return (
<Box sx={{ mt: 4, animation: 'fadeIn 0.5s ease-out' }}>
<Box sx={{ mb: 4, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box>
<Typography variant="h5" sx={{ fontWeight: 800, color: '#1e293b', mb: 1, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<AIIcon sx={{ color: '#8b5cf6', fontSize: 28 }} />
Weekly Strategic Intelligence
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
AI-generated insights for the week commencing <strong>{new Date(week_commencing).toLocaleDateString()}</strong>.
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 2 }}>
<Tooltip title="Market velocity indicates how many new pages your competitors are publishing per week.">
<Paper variant="outlined" sx={{ px: 2, py: 1, bgcolor: '#f8fafc', borderRadius: 2, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<SpeedIcon sx={{ color: '#64748b', fontSize: 20 }} />
<Box>
<Typography variant="caption" display="block" sx={{ color: '#94a3b8', fontWeight: 700, lineHeight: 1 }}>MARKET VELOCITY</Typography>
<Typography variant="subtitle2" sx={{ color: '#1e293b', fontWeight: 800 }}>{metrics.market_velocity} posts/wk</Typography>
</Box>
</Paper>
</Tooltip>
</Box>
</Box>
<Grid container spacing={3}>
{/* The Big Move - Hero Insight */}
<Grid item xs={12}>
<Card
elevation={0}
sx={{
borderRadius: 4,
background: 'linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%)',
color: 'white',
position: 'relative',
overflow: 'hidden'
}}
>
<Box sx={{ position: 'absolute', right: -20, top: -20, opacity: 0.1 }}>
<BoltIcon sx={{ fontSize: 200 }} />
</Box>
<CardContent sx={{ p: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 3 }}>
<Avatar sx={{ bgcolor: 'rgba(255,255,255,0.2)', width: 56, height: 56 }}>
<StarIcon sx={{ fontSize: 32 }} />
</Avatar>
<Box sx={{ flex: 1 }}>
<Typography variant="overline" sx={{ fontWeight: 800, letterSpacing: 2, opacity: 0.9 }}>
THE BIG MOVE
</Typography>
<Typography variant="h4" sx={{ fontWeight: 800, mb: 2 }}>
{insights.the_big_move?.insight || "Analyzing market shifts..."}
</Typography>
<Typography variant="body1" sx={{ opacity: 0.9, mb: 3, maxWidth: '800px', lineHeight: 1.7 }}>
{insights.the_big_move?.reasoning || "We've detected a significant strategic shift in your competitive landscape. Addressing this now will give you a first-mover advantage."}
</Typography>
<Box sx={{ display: 'flex', gap: 2 }}>
<Chip label={`Impact: ${insights.the_big_move?.estimated_impact || 'High'}`} sx={{ bgcolor: 'rgba(255,255,255,0.2)', color: 'white', fontWeight: 700 }} />
<Chip label={`Priority: ${insights.the_big_move?.priority || 'Critical'}`} sx={{ bgcolor: 'rgba(255,255,255,0.2)', color: 'white', fontWeight: 700 }} />
</Box>
</Box>
</Box>
</CardContent>
</Card>
</Grid>
{/* Low Hanging Fruit - Actionable Recommendations */}
<Grid item xs={12} lg={7}>
<Paper elevation={0} sx={{ p: 3, borderRadius: 4, border: '1px solid #e2e8f0', bgcolor: '#ffffff', height: '100%' }}>
<Typography variant="h6" sx={{ fontWeight: 800, color: '#1e293b', mb: 3, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<LightbulbIcon sx={{ color: '#f59e0b' }} />
Low-Hanging Fruit
</Typography>
<Typography variant="body2" sx={{ color: '#64748b', mb: 3 }}>
Topics your competitors are starting to cover that you can easily outperform with better content.
</Typography>
<List disablePadding>
{insights.low_hanging_fruit?.slice(0, 4).map((rec, idx) => (
<React.Fragment key={idx}>
<ListItem
sx={{
px: 0,
py: 2,
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start'
}}
>
<Box sx={{ display: 'flex', width: '100%', justifyContent: 'space-between', alignItems: 'flex-start', mb: 1 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#334155', flex: 1 }}>
{rec.recommendation}
</Typography>
<PriorityChip priority={rec.priority} />
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, width: '100%' }}>
<Typography variant="caption" sx={{ color: '#94a3b8', display: 'flex', alignItems: 'center', gap: 0.5 }}>
<TrendingUpIcon sx={{ fontSize: 14 }} /> Traffic: {rec.estimated_traffic}
</Typography>
<Typography variant="caption" sx={{ color: '#94a3b8', display: 'flex', alignItems: 'center', gap: 0.5 }}>
<BoltIcon sx={{ fontSize: 14 }} /> ROI: {rec.roi_estimate}
</Typography>
<Box sx={{ flexGrow: 1 }} />
{!hideCreateContent && (
<Button
size="small"
variant="text"
endIcon={<ArrowIcon />}
onClick={() => handleCreateContent(rec.recommendation)}
sx={{ fontWeight: 700, textTransform: 'none' }}
>
Create Content
</Button>
)}
</Box>
</ListItem>
{idx < 3 && <Divider />}
</React.Fragment>
))}
</List>
</Paper>
</Grid>
{/* Threat Alerts */}
<Grid item xs={12} md={5}>
<Paper elevation={0} sx={{ p: 3, borderRadius: 4, border: '1px solid #e2e8f0', bgcolor: '#fffcfc', height: '100%' }}>
<Typography variant="h6" sx={{ fontWeight: 800, color: '#1e293b', mb: 3, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<WarningIcon sx={{ color: '#ef4444' }} />
Threat Alerts
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{insights.threat_alerts?.slice(0, 3).map((threat, idx) => (
<Box
key={idx}
sx={{
p: 2,
borderRadius: 3,
bgcolor: '#ffffff',
border: '1px solid #fee2e2',
borderLeft: '4px solid #ef4444'
}}
>
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: '#991b1b', mb: 0.5 }}>
{threat.type || 'Competitive Threat'}
</Typography>
<Typography variant="body2" sx={{ color: '#475569', mb: 1.5, lineHeight: 1.5 }}>
{threat.insight}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 1 }}>
<Chip label={threat.estimated_impact} size="small" sx={{ height: 20, fontSize: '0.65rem', fontWeight: 700, bgcolor: '#fef2f2', color: '#ef4444' }} />
<Button size="small" variant="outlined" color="error" sx={{ fontSize: '0.7rem', fontWeight: 700, textTransform: 'none' }}>
Mitigation Strategy
</Button>
</Box>
</Box>
))}
{!insights.threat_alerts?.length && (
<Box sx={{ textAlign: 'center', py: 4 }}>
<CheckIcon sx={{ color: '#10b981', fontSize: 40, mb: 1, opacity: 0.5 }} />
<Typography variant="body2" sx={{ color: '#94a3b8' }}>No immediate threats detected this week.</Typography>
</Box>
)}
</Box>
</Paper>
</Grid>
</Grid>
</Box>
);
};