ALwrity version 0.5.5

This commit is contained in:
ajaysi
2025-08-13 17:38:54 +05:30
parent 66ece49705
commit 2b8c66c4d0
23 changed files with 3080 additions and 976 deletions

View File

@@ -19,13 +19,15 @@ import {
Analytics as AnalyticsIcon,
Search as SearchIcon,
Lightbulb as AIInsightsIcon,
Close as CloseIcon
Close as CloseIcon,
Add as CreateIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import ContentStrategyTab from './tabs/ContentStrategyTab';
import CalendarTab from './tabs/CalendarTab';
import AnalyticsTab from './tabs/AnalyticsTab';
import GapAnalysisTab from './tabs/GapAnalysisTab';
import CreateTab from './tabs/CreateTab';
import AIInsightsPanel from './components/AIInsightsPanel';
import ServiceStatusPanel from './components/ServiceStatusPanel';
import ProgressIndicator from './components/ProgressIndicator';
@@ -170,7 +172,8 @@ const ContentPlanningDashboard: React.FC = () => {
{ label: 'CONTENT STRATEGY', icon: <StrategyIcon />, component: <ContentStrategyTab /> },
{ label: 'CALENDAR', icon: <CalendarIcon />, component: <CalendarTab /> },
{ label: 'ANALYTICS', icon: <AnalyticsIcon />, component: <AnalyticsTab /> },
{ label: 'GAP ANALYSIS', icon: <SearchIcon />, component: <GapAnalysisTab /> }
{ label: 'GAP ANALYSIS', icon: <SearchIcon />, component: <GapAnalysisTab /> },
{ label: 'CREATE', icon: <CreateIcon />, component: <CreateTab /> }
];
const totalAIItems = (dashboardData.aiInsights?.length || 0) + (dashboardData.aiRecommendations?.length || 0);

View File

@@ -478,7 +478,7 @@ const ContentStrategyBuilder: React.FC = () => {
onUpdateFormField={updateFormField}
onValidateFormField={validateFormField}
onShowTooltip={setShowTooltip}
onViewDataSource={() => setShowDataSourceTransparency(true)}
onViewDataSource={() => setShowDataSourceTransparency(true)}
onConfirmCategoryReview={handleConfirmCategoryReviewWrapper}
onSetActiveCategory={setActiveCategory}
onSetShowEducationalInfo={setShowEducationalInfo}

View File

@@ -0,0 +1,615 @@
import React, { useState } from 'react';
import {
Dialog,
DialogContent,
DialogTitle,
Box,
Typography,
Button,
Stepper,
Step,
StepLabel,
StepContent,
Card,
CardContent,
Grid,
Chip,
IconButton,
LinearProgress,
Alert,
List,
ListItem,
ListItemIcon,
ListItemText,
Divider
} from '@mui/material';
import {
Close as CloseIcon,
CheckCircle as CheckCircleIcon,
AutoAwesome as AutoAwesomeIcon,
Psychology as PsychologyIcon,
CalendarToday as CalendarIcon,
Analytics as AnalyticsIcon,
TrendingUp as TrendingUpIcon,
Lightbulb as LightbulbIcon,
School as SchoolIcon,
Rocket as RocketIcon,
Security as SecurityIcon,
Speed as SpeedIcon,
Group as GroupIcon,
Timeline as TimelineIcon,
Assessment as AssessmentIcon,
PlayArrow as PlayArrowIcon,
Pause as PauseIcon,
Refresh as RefreshIcon,
Edit as EditIcon,
Add as AddIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
interface StrategyOnboardingDialogProps {
open: boolean;
onClose: () => void;
onConfirmStrategy: () => void;
onEditStrategy: () => void;
onCreateNewStrategy: () => void;
currentStrategy: any;
strategyStatus: 'active' | 'inactive' | 'none';
}
const StrategyOnboardingDialog: React.FC<StrategyOnboardingDialogProps> = ({
open,
onClose,
onConfirmStrategy,
onEditStrategy,
onCreateNewStrategy,
currentStrategy,
strategyStatus
}) => {
const [activeStep, setActiveStep] = useState(0);
const [loading, setLoading] = useState(false);
const steps = [
{
label: 'Welcome to ALwrity',
icon: <AutoAwesomeIcon />,
content: (
<Box>
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: 'primary.main' }}>
🚀 Your AI-Powered Content Strategy Copilot
</Typography>
<Typography variant="body1" paragraph>
ALwrity democratizes professional content strategy and calendar creation, making it accessible to solopreneurs and small businesses.
</Typography>
<Grid container spacing={2} sx={{ mt: 2 }}>
<Grid item xs={12} md={6}>
<Card sx={{ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
<PsychologyIcon sx={{ mr: 1 }} />
AI-Enhanced Strategy
</Typography>
<Typography variant="body2">
Our AI analyzes your business, competitors, and market trends to create a comprehensive content strategy.
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card sx={{ background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', color: 'white' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
<CalendarIcon sx={{ mr: 1 }} />
Smart Calendar Creation
</Typography>
<Typography variant="body2">
Automatically generate content calendars with optimal posting times and content mix.
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Box>
)
},
{
label: 'What ALwrity Has Done',
icon: <AssessmentIcon />,
content: (
<Box>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
📊 Comprehensive Research & Analysis
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom color="primary">
<SchoolIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Market Research
</Typography>
<List dense>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Industry trend analysis" />
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Competitor content audit" />
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Target audience insights" />
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Keyword opportunity analysis" />
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom color="primary">
<LightbulbIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Strategic Insights
</Typography>
<List dense>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Content pillar identification" />
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Optimal content mix" />
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Posting frequency recommendations" />
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary="Performance predictions" />
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
</Grid>
</Box>
)
},
{
label: 'Your Journey with ALwrity',
icon: <TimelineIcon />,
content: (
<Box>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
🎯 Your 4-Step Success Path
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card sx={{ border: '2px solid', borderColor: 'primary.main' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Typography variant="h4" sx={{ mr: 2, color: 'primary.main' }}>1</Typography>
<Typography variant="h6">Review & Confirm Strategy</Typography>
</Box>
<Typography variant="body2" paragraph>
Review the AI-generated content strategy tailored to your business. Make any adjustments and confirm to activate.
</Typography>
<Chip label="Current Step" color="primary" size="small" />
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Typography variant="h4" sx={{ mr: 2, color: 'text.secondary' }}>2</Typography>
<Typography variant="h6">Create Content Calendar</Typography>
</Box>
<Typography variant="body2" paragraph>
ALwrity generates a comprehensive content calendar with optimal posting times and content themes.
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Typography variant="h4" sx={{ mr: 2, color: 'text.secondary' }}>3</Typography>
<Typography variant="h6">Measure Strategy KPIs</Typography>
</Box>
<Typography variant="body2" paragraph>
Track performance metrics and analyze content effectiveness to understand what works best.
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Typography variant="h4" sx={{ mr: 2, color: 'text.secondary' }}>4</Typography>
<Typography variant="h6">Optimize with AI</Typography>
</Box>
<Typography variant="body2" paragraph>
Continuously improve your strategy based on performance data and AI recommendations.
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Box>
)
},
{
label: 'ALwrity as Your Copilot',
icon: <RocketIcon />,
content: (
<Box>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
🤖 Your AI Marketing Assistant
</Typography>
<Typography variant="body1" paragraph>
Once your strategy is active, ALwrity becomes your 24/7 content marketing copilot, handling the heavy lifting while you focus on your business.
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<Card sx={{ height: '100%', background: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
<SpeedIcon sx={{ mr: 1 }} />
Automated Execution
</Typography>
<Typography variant="body2">
ALwrity schedules, generates, reviews, and posts content according to your strategy, saving you hours every week.
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={4}>
<Card sx={{ height: '100%', background: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
<TrendingUpIcon sx={{ mr: 1 }} />
Performance Tracking
</Typography>
<Typography variant="body2">
Monitor your content performance in real-time with detailed analytics and actionable insights.
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={4}>
<Card sx={{ height: '100%', background: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
<SecurityIcon sx={{ mr: 1 }} />
Quality Assurance
</Typography>
<Typography variant="body2">
Every piece of content is reviewed for quality, brand consistency, and strategic alignment.
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
<Alert severity="info" sx={{ mt: 3 }}>
<Typography variant="body2">
<strong>Pro Tip:</strong> ALwrity learns from your content performance and continuously optimizes your strategy for better results.
</Typography>
</Alert>
</Box>
)
},
{
label: 'Take Action',
icon: <PlayArrowIcon />,
content: (
<Box>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
🎯 Ready to Activate Your Strategy?
</Typography>
{strategyStatus === 'inactive' && currentStrategy ? (
<Box>
<Alert severity="warning" sx={{ mb: 3 }}>
<Typography variant="body1">
<strong>Strategy Found:</strong> We found an existing strategy that needs to be activated.
</Typography>
</Alert>
<Card sx={{ mb: 3, border: '2px solid', borderColor: 'warning.main' }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Current Strategy: {currentStrategy.name}
</Typography>
<Typography variant="body2" paragraph>
{currentStrategy.description}
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip label={`Industry: ${currentStrategy.industry}`} color="primary" variant="outlined" />
<Chip label={`Target: ${currentStrategy.target_audience}`} color="secondary" variant="outlined" />
</Box>
</CardContent>
</Card>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Button
fullWidth
variant="contained"
size="large"
onClick={onConfirmStrategy}
startIcon={<CheckCircleIcon />}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'&:hover': { transform: 'translateY(-2px)' },
transition: 'all 0.3s ease'
}}
>
Activate Strategy
</Button>
</Grid>
<Grid item xs={12} md={4}>
<Button
fullWidth
variant="outlined"
size="large"
onClick={onEditStrategy}
startIcon={<EditIcon />}
>
Edit Strategy
</Button>
</Grid>
<Grid item xs={12} md={4}>
<Button
fullWidth
variant="outlined"
size="large"
onClick={onCreateNewStrategy}
startIcon={<AddIcon />}
>
Create New
</Button>
</Grid>
</Grid>
</Box>
) : strategyStatus === 'none' ? (
<Box>
<Alert severity="info" sx={{ mb: 3 }}>
<Typography variant="body1">
<strong>No Strategy Found:</strong> Let's create your first content strategy!
</Typography>
</Alert>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Button
fullWidth
variant="contained"
size="large"
onClick={onCreateNewStrategy}
startIcon={<AutoAwesomeIcon />}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'&:hover': { transform: 'translateY(-2px)' },
transition: 'all 0.3s ease'
}}
>
Create Strategy with AI
</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
fullWidth
variant="outlined"
size="large"
onClick={onClose}
startIcon={<CloseIcon />}
>
Maybe Later
</Button>
</Grid>
</Grid>
</Box>
) : (
<Box>
<Alert severity="success" sx={{ mb: 3 }}>
<Typography variant="body1">
<strong>Strategy Active:</strong> Your content strategy is already active and running!
</Typography>
</Alert>
<Button
fullWidth
variant="contained"
size="large"
onClick={onClose}
startIcon={<CheckCircleIcon />}
sx={{
background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)',
'&:hover': { transform: 'translateY(-2px)' },
transition: 'all 0.3s ease'
}}
>
Continue to Dashboard
</Button>
</Box>
)}
</Box>
)
}
];
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
const handleReset = () => {
setActiveStep(0);
};
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="lg"
fullWidth
PaperProps={{
sx: {
background: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%)',
color: 'white',
borderRadius: 3,
minHeight: '80vh'
}
}}
>
<DialogTitle sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<AutoAwesomeIcon sx={{ mr: 2, fontSize: 32 }} />
<Typography variant="h5" sx={{ fontWeight: 600 }}>
ALwrity Content Strategy Onboarding
</Typography>
</Box>
<IconButton onClick={onClose} sx={{ color: 'white' }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent sx={{ p: 4 }}>
<Box sx={{ maxWidth: 1200, mx: 'auto' }}>
<Stepper activeStep={activeStep} orientation="vertical" sx={{ mb: 4 }}>
{steps.map((step, index) => (
<Step key={step.label}>
<StepLabel
StepIconComponent={() => (
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: activeStep >= index ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'rgba(255,255,255,0.1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white'
}}>
{step.icon}
</Box>
)}
>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
{step.label}
</Typography>
</StepLabel>
<StepContent>
<AnimatePresence mode="wait">
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<Box sx={{ mb: 3 }}>
{step.content}
</Box>
{index < steps.length - 1 && (
<Box sx={{ display: 'flex', gap: 2 }}>
<Button
variant="contained"
onClick={handleNext}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'&:hover': { transform: 'translateY(-2px)' },
transition: 'all 0.3s ease'
}}
>
{index === steps.length - 2 ? 'Finish' : 'Continue'}
</Button>
<Button
disabled={index === 0}
onClick={handleBack}
variant="outlined"
sx={{ color: 'white', borderColor: 'white' }}
>
Back
</Button>
</Box>
)}
</motion.div>
</AnimatePresence>
</StepContent>
</Step>
))}
</Stepper>
{activeStep === steps.length && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<Box sx={{ textAlign: 'center', py: 4 }}>
<CheckCircleIcon sx={{ fontSize: 64, color: '#4caf50', mb: 2 }} />
<Typography variant="h5" gutterBottom>
Welcome to ALwrity!
</Typography>
<Typography variant="body1" paragraph>
You're now ready to start your content marketing journey with AI assistance.
</Typography>
<Button
variant="contained"
onClick={handleReset}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'&:hover': { transform: 'translateY(-2px)' },
transition: 'all 0.3s ease'
}}
>
Start Over
</Button>
</Box>
</motion.div>
)}
</Box>
</DialogContent>
</Dialog>
);
};
export default StrategyOnboardingDialog;

View File

@@ -8,7 +8,6 @@ import {
TextField,
Card,
CardContent,
CardActions,
Chip,
IconButton,
Dialog,
@@ -42,7 +41,6 @@ import {
CalendarToday as CalendarIcon,
Event as EventIcon,
Refresh as RefreshIcon,
AutoAwesome as AIIcon,
TrendingUp as TrendingIcon,
ContentCopy as RepurposeIcon,
Analytics as AnalyticsIcon,
@@ -64,7 +62,6 @@ import {
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import CalendarGenerationWizard from '../components/CalendarGenerationWizard';
interface TabPanelProps {
children?: React.ReactNode;
@@ -100,10 +97,8 @@ const CalendarTab: React.FC = () => {
updateCalendarEvents,
// New calendar generation state
generatedCalendar,
contentOptimization,
performancePrediction,
contentRepurposing,
trendingTopics,
aiInsights,
calendarGenerationError,
dataLoading
@@ -131,7 +126,7 @@ const CalendarTab: React.FC = () => {
aiAnalysisResults: []
});
const [calendarGenerationMode, setCalendarGenerationMode] = useState<'transparency' | 'wizard'>('transparency');
const safeCalendarEvents = Array.isArray(calendarEvents) ? calendarEvents : [];
useEffect(() => {
loadCalendarData();
@@ -214,22 +209,6 @@ const CalendarTab: React.FC = () => {
await loadCalendarData();
};
const handleGenerateAICalendar = async () => {
try {
// This will now use the comprehensive data from the transparency dashboard
const calendarConfig = {
userData,
calendarType: 'monthly',
industry: userData.onboardingData?.industry || 'technology',
businessSize: 'sme'
};
await contentPlanningApi.generateComprehensiveCalendar(calendarConfig);
} catch (error) {
console.error('Error generating AI calendar:', error);
}
};
const handleDataUpdate = (updatedData: any) => {
setUserData((prev: any) => ({ ...prev, ...updatedData }));
};
@@ -278,9 +257,6 @@ const CalendarTab: React.FC = () => {
}
};
// Ensure calendarEvents is always an array
const safeCalendarEvents = Array.isArray(calendarEvents) ? calendarEvents : [];
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
@@ -321,9 +297,6 @@ const CalendarTab: React.FC = () => {
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={(e, newValue) => setTabValue(newValue)}>
<Tab label="Calendar Events" icon={<CalendarIcon />} iconPosition="start" />
<Tab label="Calendar Wizard" icon={<AIIcon />} iconPosition="start" />
<Tab label="Content Optimizer" icon={<AnalyticsIcon />} iconPosition="start" />
<Tab label="Trending Topics" icon={<TrendingIcon />} iconPosition="start" />
</Tabs>
</Box>
@@ -416,102 +389,6 @@ const CalendarTab: React.FC = () => {
)}
</TabPanel>
<TabPanel value={tabValue} index={1}>
{/* Calendar Generation Wizard with Data Transparency */}
<CalendarGenerationWizard
userData={userData}
onGenerateCalendar={handleGenerateCalendar}
loading={loading}
/>
</TabPanel>
<TabPanel value={tabValue} index={2}>
{/* Content Optimizer Tab */}
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Optimization
</Typography>
{contentOptimization ? (
<Box>
<Typography variant="body1" gutterBottom>
Optimization Recommendations
</Typography>
<List>
{contentOptimization.recommendations?.map((rec: any, index: number) => (
<ListItem key={index}>
<ListItemIcon>
<LightbulbIcon color="primary" />
</ListItemIcon>
<ListItemText
primary={rec.title}
secondary={rec.description}
/>
</ListItem>
))}
</List>
</Box>
) : (
<Box sx={{ textAlign: 'center', py: 4 }}>
<AnalyticsIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" color="text.secondary" gutterBottom>
No optimization data
</Typography>
<Typography variant="body2" color="text.secondary">
Generate content optimization recommendations
</Typography>
</Box>
)}
</Paper>
</Grid>
</Grid>
</TabPanel>
<TabPanel value={tabValue} index={3}>
{/* Trending Topics Tab */}
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Trending Topics
</Typography>
{trendingTopics ? (
<Box>
<Typography variant="body1" gutterBottom>
Current Trending Topics
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{trendingTopics.trending_topics?.map((topic: any, index: number) => (
<Chip
key={index}
label={topic.name || topic.keyword}
color="primary"
variant="outlined"
/>
))}
</Box>
</Box>
) : (
<Box sx={{ textAlign: 'center', py: 4 }}>
<TrendingIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" color="text.secondary" gutterBottom>
No trending topics
</Typography>
<Typography variant="body2" color="text.secondary">
Get trending topics for your industry
</Typography>
</Box>
)}
</Paper>
</Grid>
</Grid>
</TabPanel>
{/* Event Dialog */}
<Dialog open={dialogOpen} onClose={handleCloseDialog} maxWidth="sm" fullWidth>
<DialogTitle>

View File

@@ -0,0 +1,73 @@
import React from 'react';
import {
Box,
Grid,
Paper,
Typography,
List,
ListItem,
ListItemText,
ListItemIcon,
Chip
} from '@mui/material';
import {
Analytics as AnalyticsIcon,
Lightbulb as LightbulbIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
const ContentOptimizerTab: React.FC = () => {
const { contentOptimization } = useContentPlanningStore();
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Content Optimizer
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Optimization
</Typography>
{contentOptimization ? (
<Box>
<Typography variant="body1" gutterBottom>
Optimization Recommendations
</Typography>
<List>
{contentOptimization.recommendations?.map((rec: any, index: number) => (
<ListItem key={index}>
<ListItemIcon>
<LightbulbIcon color="primary" />
</ListItemIcon>
<ListItemText
primary={rec.title}
secondary={rec.description}
/>
</ListItem>
))}
</List>
</Box>
) : (
<Box sx={{ textAlign: 'center', py: 4 }}>
<AnalyticsIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" color="text.secondary" gutterBottom>
No optimization data
</Typography>
<Typography variant="body2" color="text.secondary">
Generate content optimization recommendations
</Typography>
</Box>
)}
</Paper>
</Grid>
</Grid>
</Box>
);
};
export default ContentOptimizerTab;

View File

@@ -0,0 +1,125 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Grid,
Card,
CardContent,
CardActions,
Typography,
Button,
CircularProgress
} from '@mui/material';
import {
PieChart as PieChartIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
const ContentPillarsTab: React.FC = () => {
const { currentStrategy } = useContentPlanningStore();
const [contentPillars, setContentPillars] = useState<any[]>([]);
const [dataLoading, setDataLoading] = useState(false);
useEffect(() => {
loadContentPillars();
}, [currentStrategy]);
const loadContentPillars = async () => {
try {
setDataLoading(true);
// Get content pillars from current strategy
if (currentStrategy && currentStrategy.content_pillars) {
const pillars = currentStrategy.content_pillars.map((pillar: any, index: number) => ({
name: pillar.name || `Pillar ${index + 1}`,
content_count: pillar.content_count || Math.floor(Math.random() * 20) + 5,
avg_engagement: pillar.avg_engagement || (Math.random() * 30 + 60).toFixed(1),
performance_score: pillar.performance_score || (Math.random() * 20 + 75).toFixed(0)
}));
setContentPillars(pillars);
} else {
// Default pillars if no strategy exists
setContentPillars([
{ name: 'Educational Content', content_count: 15, avg_engagement: 78.5, performance_score: 85 },
{ name: 'Thought Leadership', content_count: 8, avg_engagement: 92.3, performance_score: 91 },
{ name: 'Case Studies', content_count: 12, avg_engagement: 85.7, performance_score: 88 },
{ name: 'Industry Insights', content_count: 10, avg_engagement: 79.2, performance_score: 82 }
]);
}
} catch (error) {
console.error('Error loading content pillars:', error);
} finally {
setDataLoading(false);
}
};
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Content Pillars
</Typography>
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : contentPillars.length > 0 ? (
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom>
Content Pillars Overview
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics.
</Typography>
</Grid>
{contentPillars.map((pillar, index) => (
<Grid item xs={12} md={6} key={index}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
{pillar.name}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Content Count
</Typography>
<Typography variant="h6">
{pillar.content_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Avg. Engagement
</Typography>
<Typography variant="h6">
{pillar.avg_engagement}%
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary">
Performance Score
</Typography>
<Typography variant="h6" color="success.main">
{pillar.performance_score}/100
</Typography>
</Box>
</CardContent>
<CardActions>
<Button size="small">View Content</Button>
<Button size="small">Optimize</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No content pillars data available
</Typography>
)}
</Box>
);
};
export default ContentPillarsTab;

View File

@@ -5,12 +5,10 @@ import {
Paper,
Typography,
Button,
TextField,
Card,
CardContent,
CardActions,
Chip,
Divider,
Alert,
List,
ListItem,
@@ -39,7 +37,6 @@ import {
Lightbulb as LightbulbIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Search as SearchIcon,
Analytics as AnalyticsIcon,
Timeline as TimelineIcon,
Assessment as AssessmentIcon,
@@ -48,36 +45,14 @@ import {
Add as AddIcon,
Edit as EditIcon,
Visibility as VisibilityIcon,
BarChart as BarChartIcon,
PieChart as PieChartIcon,
ShowChart as ShowChartIcon,
AutoAwesome as AutoAwesomeIcon
AutoAwesome as AutoAwesomeIcon,
PlayArrow as PlayArrowIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
import StrategyIntelligenceTab from '../components/StrategyIntelligenceTab';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`strategy-tabpanel-${index}`}
aria-labelledby={`strategy-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
</div>
);
}
import StrategyOnboardingDialog from '../components/StrategyOnboardingDialog';
const ContentStrategyTab: React.FC = () => {
const {
@@ -85,7 +60,6 @@ const ContentStrategyTab: React.FC = () => {
currentStrategy,
aiInsights,
aiRecommendations,
performanceMetrics,
loading,
error,
loadStrategies,
@@ -93,7 +67,6 @@ const ContentStrategyTab: React.FC = () => {
loadAIRecommendations
} = useContentPlanningStore();
const [tabValue, setTabValue] = useState(0);
const [strategyForm, setStrategyForm] = useState({
name: '',
description: '',
@@ -104,25 +77,53 @@ const ContentStrategyTab: React.FC = () => {
// Real data states
const [strategicIntelligence, setStrategicIntelligence] = useState<any>(null);
const [keywordResearch, setKeywordResearch] = useState<any>(null);
const [contentPillars, setContentPillars] = useState<any[]>([]);
const [dataLoading, setDataLoading] = useState({
strategies: false,
insights: false,
recommendations: false,
strategicIntelligence: false,
keywordResearch: false,
pillars: false
strategicIntelligence: false
});
// Strategy status and onboarding
const [strategyStatus, setStrategyStatus] = useState<'active' | 'inactive' | 'none'>('none');
const [showOnboarding, setShowOnboarding] = useState(false);
const [hasCheckedStrategy, setHasCheckedStrategy] = useState(false);
// Load data on component mount
useEffect(() => {
loadInitialData();
}, []);
// Check strategy status when strategies are loaded
useEffect(() => {
if (strategies && strategies.length > 0 && !hasCheckedStrategy) {
checkStrategyStatus();
} else if ((!strategies || strategies.length === 0) && !hasCheckedStrategy) {
setStrategyStatus('none');
setHasCheckedStrategy(true);
setShowOnboarding(true);
}
}, [strategies, hasCheckedStrategy]);
const checkStrategyStatus = () => {
if (strategies && strategies.length > 0) {
// Find the most recent strategy
const latestStrategy = strategies[0]; // Assuming strategies are sorted by date
// For now, we'll assume strategies are active if they exist
// In a real implementation, you would check a status field from the database
setStrategyStatus('active');
setShowOnboarding(false);
} else {
setStrategyStatus('none');
setShowOnboarding(true);
}
setHasCheckedStrategy(true);
};
const loadInitialData = async () => {
try {
setDataLoading({ strategies: true, insights: true, recommendations: true, strategicIntelligence: true, keywordResearch: true, pillars: true });
setDataLoading({ strategies: true, insights: true, recommendations: true, strategicIntelligence: true });
// Load strategies
await loadStrategies();
@@ -136,16 +137,10 @@ const ContentStrategyTab: React.FC = () => {
// Load strategic intelligence
await loadStrategicIntelligence();
// Load keyword research
await loadKeywordResearch();
// Load content pillars
await loadContentPillars();
} catch (error) {
console.error('Error loading initial data:', error);
} finally {
setDataLoading({ strategies: false, insights: false, recommendations: false, strategicIntelligence: false, keywordResearch: false, pillars: false });
setDataLoading({ strategies: false, insights: false, recommendations: false, strategicIntelligence: false });
}
};
@@ -236,156 +231,6 @@ const ContentStrategyTab: React.FC = () => {
}
};
const loadKeywordResearch = async () => {
try {
setDataLoading(prev => ({ ...prev, keywordResearch: true }));
// Use streaming endpoint for real-time updates
const eventSource = await contentPlanningApi.streamKeywordResearch(1);
contentPlanningApi.handleSSEData(
eventSource,
(data) => {
console.log('Keyword Research SSE Data:', data);
if (data.type === 'status') {
// Update loading message
console.log('Status:', data.message);
} else if (data.type === 'progress') {
// Update progress (could be used for progress bar)
console.log('Progress:', data.progress, '%');
} else if (data.type === 'result' && data.status === 'success') {
// Set the keyword research data
setKeywordResearch(data.data);
setDataLoading(prev => ({ ...prev, keywordResearch: false }));
} else if (data.type === 'error') {
console.error('Keyword Research Error:', data.message);
// Set fallback data on error
const keywordData = {
trend_analysis: {
high_volume_keywords: [
{ keyword: 'AI marketing automation', volume: '10K-100K', difficulty: 'Medium' },
{ keyword: 'content strategy 2024', volume: '1K-10K', difficulty: 'Low' },
{ keyword: 'digital marketing trends', volume: '10K-100K', difficulty: 'High' }
],
trending_keywords: [
{ keyword: 'AI content generation', growth: '+45%', opportunity: 'High' },
{ keyword: 'voice search optimization', growth: '+32%', opportunity: 'Medium' },
{ keyword: 'video marketing strategy', growth: '+28%', opportunity: 'High' }
]
},
intent_analysis: {
informational: ['how to', 'what is', 'guide to'],
navigational: ['company name', 'brand name', 'website'],
transactional: ['buy', 'purchase', 'download', 'sign up']
},
opportunities: [
{ keyword: 'AI content tools', search_volume: '5K-10K', competition: 'Low', cpc: '$2.50' },
{ keyword: 'content marketing ROI', search_volume: '1K-5K', competition: 'Medium', cpc: '$4.20' },
{ keyword: 'social media strategy', search_volume: '10K-50K', competition: 'High', cpc: '$3.80' }
]
};
setKeywordResearch(keywordData);
setDataLoading(prev => ({ ...prev, keywordResearch: false }));
}
},
(error) => {
console.error('Keyword Research SSE Error:', error);
// Set fallback data on error
const keywordData = {
trend_analysis: {
high_volume_keywords: [
{ keyword: 'AI marketing automation', volume: '10K-100K', difficulty: 'Medium' },
{ keyword: 'content strategy 2024', volume: '1K-10K', difficulty: 'Low' },
{ keyword: 'digital marketing trends', volume: '10K-100K', difficulty: 'High' }
],
trending_keywords: [
{ keyword: 'AI content generation', growth: '+45%', opportunity: 'High' },
{ keyword: 'voice search optimization', growth: '+32%', opportunity: 'Medium' },
{ keyword: 'video marketing strategy', growth: '+28%', opportunity: 'High' }
]
},
intent_analysis: {
informational: ['how to', 'what is', 'guide to'],
navigational: ['company name', 'brand name', 'website'],
transactional: ['buy', 'purchase', 'download', 'sign up']
},
opportunities: [
{ keyword: 'AI content tools', search_volume: '5K-10K', competition: 'Low', cpc: '$2.50' },
{ keyword: 'content marketing ROI', search_volume: '1K-5K', competition: 'Medium', cpc: '$4.20' },
{ keyword: 'social media strategy', search_volume: '10K-50K', competition: 'High', cpc: '$3.80' }
]
};
setKeywordResearch(keywordData);
setDataLoading(prev => ({ ...prev, keywordResearch: false }));
}
);
} catch (error) {
console.error('Error loading keyword research:', error);
// Set fallback data on error
const keywordData = {
trend_analysis: {
high_volume_keywords: [
{ keyword: 'AI marketing automation', volume: '10K-100K', difficulty: 'Medium' },
{ keyword: 'content strategy 2024', volume: '1K-10K', difficulty: 'Low' },
{ keyword: 'digital marketing trends', volume: '10K-100K', difficulty: 'High' }
],
trending_keywords: [
{ keyword: 'AI content generation', growth: '+45%', opportunity: 'High' },
{ keyword: 'voice search optimization', growth: '+32%', opportunity: 'Medium' },
{ keyword: 'video marketing strategy', growth: '+28%', opportunity: 'High' }
]
},
intent_analysis: {
informational: ['how to', 'what is', 'guide to'],
navigational: ['company name', 'brand name', 'website'],
transactional: ['buy', 'purchase', 'download', 'sign up']
},
opportunities: [
{ keyword: 'AI content tools', search_volume: '5K-10K', competition: 'Low', cpc: '$2.50' },
{ keyword: 'content marketing ROI', search_volume: '1K-5K', competition: 'Medium', cpc: '$4.20' },
{ keyword: 'social media strategy', search_volume: '10K-50K', competition: 'High', cpc: '$3.80' }
]
};
setKeywordResearch(keywordData);
setDataLoading(prev => ({ ...prev, keywordResearch: false }));
}
};
const loadContentPillars = async () => {
try {
setDataLoading(prev => ({ ...prev, pillars: true }));
// Get content pillars from current strategy
if (currentStrategy && currentStrategy.content_pillars) {
const pillars = currentStrategy.content_pillars.map((pillar: any, index: number) => ({
name: pillar.name || `Pillar ${index + 1}`,
content_count: pillar.content_count || Math.floor(Math.random() * 20) + 5,
avg_engagement: pillar.avg_engagement || (Math.random() * 30 + 60).toFixed(1),
performance_score: pillar.performance_score || (Math.random() * 20 + 75).toFixed(0)
}));
setContentPillars(pillars);
} else {
// Default pillars if no strategy exists
setContentPillars([
{ name: 'Educational Content', content_count: 15, avg_engagement: 78.5, performance_score: 85 },
{ name: 'Thought Leadership', content_count: 8, avg_engagement: 92.3, performance_score: 91 },
{ name: 'Case Studies', content_count: 12, avg_engagement: 85.7, performance_score: 88 },
{ name: 'Industry Insights', content_count: 10, avg_engagement: 79.2, performance_score: 82 }
]);
}
} catch (error) {
console.error('Error loading content pillars:', error);
} finally {
setDataLoading(prev => ({ ...prev, pillars: false }));
}
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleStrategyFormChange = (field: string, value: string) => {
setStrategyForm(prev => ({
...prev,
@@ -428,6 +273,38 @@ const ContentStrategyTab: React.FC = () => {
await loadInitialData();
};
// Onboarding dialog handlers
const handleConfirmStrategy = async () => {
try {
if (currentStrategy) {
// For now, we'll just close the dialog since we can't update status
// In a real implementation, you would update the strategy status in the database
setShowOnboarding(false);
// Reload strategies to get updated data
await loadStrategies();
}
} catch (error) {
console.error('Error activating strategy:', error);
}
};
const handleEditStrategy = () => {
setShowOnboarding(false);
// Navigate to Create tab to edit strategy
// This would typically involve changing the active tab in the parent component
};
const handleCreateNewStrategy = () => {
setShowOnboarding(false);
// Navigate to Create tab to create new strategy
// This would typically involve changing the active tab in the parent component
};
const handleCloseOnboarding = () => {
setShowOnboarding(false);
};
return (
<Box sx={{ p: 3 }}>
{error && (
@@ -436,260 +313,72 @@ const ContentStrategyTab: React.FC = () => {
</Alert>
)}
{/* Strategy Builder Tabs */}
{/* Strategy Status Banner */}
{strategyStatus === 'inactive' && (
<Alert
severity="warning"
sx={{ mb: 3 }}
action={
<Button
color="inherit"
size="small"
onClick={() => setShowOnboarding(true)}
startIcon={<PlayArrowIcon />}
>
Activate Strategy
</Button>
}
>
<Typography variant="body1">
<strong>Strategy Pending Activation:</strong> Your content strategy is ready but needs to be activated to start your AI-powered content marketing journey.
</Typography>
</Alert>
)}
{strategyStatus === 'none' && (
<Alert
severity="info"
sx={{ mb: 3 }}
action={
<Button
color="inherit"
size="small"
onClick={() => setShowOnboarding(true)}
startIcon={<AutoAwesomeIcon />}
>
Create Strategy
</Button>
}
>
<Typography variant="body1">
<strong>No Strategy Found:</strong> Let's create your first AI-powered content strategy to start your digital marketing journey.
</Typography>
</Alert>
)}
{strategyStatus === 'active' && (
<Alert severity="success" sx={{ mb: 3 }}>
<Typography variant="body1">
<strong>Strategy Active:</strong> Your content strategy is running and ALwrity is managing your content marketing automatically.
</Typography>
</Alert>
)}
{/* Strategic Intelligence */}
<Paper sx={{ width: '100%', mb: 3 }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={tabValue} onChange={handleTabChange} aria-label="strategy builder tabs">
<Tab
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<AutoAwesomeIcon sx={{ mr: 1 }} />
Enhanced Strategy Builder
</Box>
}
/>
<Tab label="Strategic Intelligence" icon={<AssessmentIcon />} />
<Tab label="Keyword Research" icon={<SearchIcon />} />
<Tab label="Performance Analytics" icon={<BarChartIcon />} />
<Tab label="Content Pillars" icon={<PieChartIcon />} />
</Tabs>
</Box>
{/* Enhanced Strategy Builder Tab */}
<TabPanel value={tabValue} index={0}>
<ContentStrategyBuilder />
</TabPanel>
{/* Strategic Intelligence Tab */}
<TabPanel value={tabValue} index={1}>
<StrategyIntelligenceTab />
</TabPanel>
{/* Keyword Research Tab */}
<TabPanel value={tabValue} index={2}>
{dataLoading.keywordResearch ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : keywordResearch && keywordResearch.trend_analysis ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
High Volume Keywords
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Volume</TableCell>
<TableCell>Difficulty</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => (
<TableRow key={index}>
<TableCell>{keyword.keyword}</TableCell>
<TableCell>{keyword.volume}</TableCell>
<TableCell>
<Chip
label={keyword.difficulty}
color={keyword.difficulty === 'Low' ? 'success' : keyword.difficulty === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Trending Keywords
</Typography>
{(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{keyword.keyword}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={keyword.growth}
color="success"
size="small"
/>
<Chip
label={keyword.opportunity}
color={keyword.opportunity === 'High' ? 'success' : 'primary'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Keyword Opportunities
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Search Volume</TableCell>
<TableCell>Competition</TableCell>
<TableCell>CPC</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.opportunities || []).map((opportunity: any, index: number) => (
<TableRow key={index}>
<TableCell>{opportunity.keyword}</TableCell>
<TableCell>{opportunity.search_volume}</TableCell>
<TableCell>
<Chip
label={opportunity.competition}
color={opportunity.competition === 'Low' ? 'success' : opportunity.competition === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
<TableCell>${opportunity.cpc}</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Add to Strategy
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No keyword research data available
</Typography>
)}
</TabPanel>
{/* Performance Analytics Tab */}
<TabPanel value={tabValue} index={3}>
{performanceMetrics ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Content Performance by Type
</Typography>
<Typography variant="body2" color="text.secondary">
No content performance data available
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Growth Trends
</Typography>
<Typography variant="body2" color="text.secondary">
No trend data available
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No performance analytics data available
</Typography>
)}
</TabPanel>
{/* Content Pillars Tab */}
<TabPanel value={tabValue} index={4}>
{dataLoading.pillars ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : contentPillars.length > 0 ? (
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom>
Content Pillars Overview
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics.
</Typography>
</Grid>
{contentPillars.map((pillar, index) => (
<Grid item xs={12} md={6} key={index}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
{pillar.name}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Content Count
</Typography>
<Typography variant="h6">
{pillar.content_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Avg. Engagement
</Typography>
<Typography variant="h6">
{pillar.avg_engagement}%
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary">
Performance Score
</Typography>
<Typography variant="h6" color="success.main">
{pillar.performance_score}/100
</Typography>
</Box>
</CardContent>
<CardActions>
<Button size="small">View Content</Button>
<Button size="small">Optimize</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No content pillars data available
</Typography>
)}
</TabPanel>
<StrategyIntelligenceTab />
</Paper>
{/* Strategy Onboarding Dialog */}
<StrategyOnboardingDialog
open={showOnboarding}
onClose={handleCloseOnboarding}
onConfirmStrategy={handleConfirmStrategy}
onEditStrategy={handleEditStrategy}
onCreateNewStrategy={handleCreateNewStrategy}
currentStrategy={currentStrategy}
strategyStatus={strategyStatus}
/>
</Box>
);
};

View File

@@ -0,0 +1,113 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Tabs,
Tab,
Typography
} from '@mui/material';
import {
AutoAwesome as AutoAwesomeIcon,
CalendarToday as CalendarIcon
} from '@mui/icons-material';
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
import CalendarGenerationWizard from '../components/CalendarGenerationWizard';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`create-tabpanel-${index}`}
aria-labelledby={`create-tab-${index}`}
{...other}
>
{value === index && <Box>{children}</Box>}
</div>
);
}
const CreateTab: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [userData, setUserData] = useState<any>({});
useEffect(() => {
loadUserData();
}, []);
const loadUserData = async () => {
try {
// Load comprehensive user data for calendar generation
const comprehensiveData = await contentPlanningApi.getComprehensiveUserData(1); // Pass user ID
setUserData(comprehensiveData.data); // Extract the data from the response
} catch (error) {
console.error('Error loading user data:', error);
}
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleGenerateCalendar = async (calendarConfig: any) => {
try {
await contentPlanningApi.generateComprehensiveCalendar({
...calendarConfig,
userData
});
} catch (error) {
console.error('Error generating calendar:', error);
}
};
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Create
</Typography>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange} aria-label="create tabs">
<Tab
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<AutoAwesomeIcon sx={{ mr: 1 }} />
Enhanced Strategy Builder
</Box>
}
/>
<Tab
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<CalendarIcon sx={{ mr: 1 }} />
Calendar Wizard
</Box>
}
/>
</Tabs>
</Box>
<TabPanel value={tabValue} index={0}>
<ContentStrategyBuilder />
</TabPanel>
<TabPanel value={tabValue} index={1}>
<CalendarGenerationWizard
userData={userData}
onGenerateCalendar={handleGenerateCalendar}
loading={false}
/>
</TabPanel>
</Box>
);
};
export default CreateTab;

View File

@@ -1,404 +1,95 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import {
Box,
Grid,
Paper,
Tabs,
Tab,
Typography,
Button,
TextField,
Card,
CardContent,
Chip,
Divider,
Alert,
CircularProgress,
List,
ListItem,
ListItemText,
ListItemIcon
Alert
} from '@mui/material';
import {
Analytics as AnalyticsIcon,
TrendingUp as TrendingIcon,
Search as SearchIcon,
Add as AddIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
Assessment as AssessmentIcon
Assessment as AssessmentIcon,
BarChart as BarChartIcon,
PieChart as PieChartIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import RefineAnalysisTab from './RefineAnalysisTab';
import ContentOptimizerTab from './ContentOptimizerTab';
import TrendingTopicsTab from './TrendingTopicsTab';
import KeywordResearchTab from './KeywordResearchTab';
import PerformanceAnalyticsTab from './PerformanceAnalyticsTab';
import ContentPillarsTab from './ContentPillarsTab';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`gap-analysis-tabpanel-${index}`}
aria-labelledby={`gap-analysis-tab-${index}`}
{...other}
>
{value === index && <Box>{children}</Box>}
</div>
);
}
const GapAnalysisTab: React.FC = () => {
const {
gapAnalyses,
loading,
error,
loadGapAnalyses,
analyzeContentGaps,
updateGapAnalyses
} = useContentPlanningStore();
const [analysisForm, setAnalysisForm] = useState({
website_url: '',
competitors: [] as string[],
keywords: [] as string[]
});
const [newCompetitor, setNewCompetitor] = useState('');
const [newKeyword, setNewKeyword] = useState('');
const [dataLoading, setDataLoading] = useState(false);
const [tabValue, setTabValue] = useState(0);
useEffect(() => {
loadGapAnalysisData();
}, []);
const loadGapAnalysisData = async () => {
try {
setDataLoading(true);
const response = await contentPlanningApi.getGapAnalysesSafe();
console.log('Gap Analysis Response:', response);
// Transform the backend response to match frontend expectations
if (response && response.gap_analyses) {
const transformedAnalyses = response.gap_analyses.map((analysis: any, index: number) => ({
id: analysis.id || `analysis_${index}`,
website_url: analysis.website_url || 'example.com',
competitors: analysis.competitors || [],
keywords: analysis.keywords || [],
gaps: analysis.gaps || [],
recommendations: analysis.recommendations || [],
created_at: analysis.created_at || new Date().toISOString()
}));
console.log('Transformed Analyses:', transformedAnalyses);
// Update the store with transformed data
updateGapAnalyses(transformedAnalyses);
} else {
console.log('No gap analyses found in response');
updateGapAnalyses([]);
}
} catch (error) {
console.error('Error loading gap analysis data:', error);
updateGapAnalyses([]);
} finally {
setDataLoading(false);
}
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleAddCompetitor = () => {
if (newCompetitor.trim() && !analysisForm.competitors.includes(newCompetitor.trim())) {
setAnalysisForm(prev => ({
...prev,
competitors: [...prev.competitors, newCompetitor.trim()]
}));
setNewCompetitor('');
}
};
const handleRemoveCompetitor = (competitorToRemove: string) => {
setAnalysisForm(prev => ({
...prev,
competitors: prev.competitors.filter(comp => comp !== competitorToRemove)
}));
};
const handleAddKeyword = () => {
if (newKeyword.trim() && !analysisForm.keywords.includes(newKeyword.trim())) {
setAnalysisForm(prev => ({
...prev,
keywords: [...prev.keywords, newKeyword.trim()]
}));
setNewKeyword('');
}
};
const handleRemoveKeyword = (keywordToRemove: string) => {
setAnalysisForm(prev => ({
...prev,
keywords: prev.keywords.filter(keyword => keyword !== keywordToRemove)
}));
};
const handleRunAnalysis = async () => {
if (!analysisForm.website_url) {
return;
}
try {
setDataLoading(true);
await analyzeContentGaps({
website_url: analysisForm.website_url,
competitors: analysisForm.competitors,
keywords: analysisForm.keywords
});
// Reload data after analysis
await loadGapAnalyses();
// Reset form
setAnalysisForm({
website_url: '',
competitors: [],
keywords: []
});
} catch (error) {
console.error('Error running gap analysis:', error);
} finally {
setDataLoading(false);
}
};
// Ensure gapAnalyses is always an array and transform the data structure
const safeGapAnalyses = Array.isArray(gapAnalyses) ? gapAnalyses : [];
// Transform backend data structure to frontend expected structure
const transformedGapAnalyses = safeGapAnalyses.map((analysis, index) => {
// Handle the actual backend structure: { recommendations: [...] }
const recommendations = analysis.recommendations || [];
return {
id: analysis.id || `analysis-${index}`,
website_url: analysis.website_url || 'Unknown Website',
competitors: analysis.competitors || [],
keywords: analysis.keywords || [],
recommendations: recommendations,
created_at: analysis.created_at || new Date().toISOString(),
// Extract gaps from recommendations if available
gaps: recommendations.length > 0 ?
recommendations.filter((rec: any) => rec.type === 'gap').map((rec: any) => rec.title || rec.description || 'Content gap identified') :
[]
};
});
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Content Gap Analysis
Gap Analysis & Optimization
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange} aria-label="gap analysis tabs">
<Tab label="Content Optimizer" icon={<AnalyticsIcon />} iconPosition="start" />
<Tab label="Trending Topics" icon={<TrendingIcon />} iconPosition="start" />
<Tab label="Keyword Research" icon={<SearchIcon />} iconPosition="start" />
<Tab label="Performance Analytics" icon={<BarChartIcon />} iconPosition="start" />
<Tab label="Content Pillars" icon={<PieChartIcon />} iconPosition="start" />
<Tab label="Refine Analysis" icon={<AssessmentIcon />} iconPosition="start" />
</Tabs>
</Box>
<Grid container spacing={3}>
{/* Analysis Setup */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<SearchIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Analysis Setup
</Typography>
<Divider sx={{ mb: 2 }} />
<TextField
fullWidth
label="Website URL"
value={analysisForm.website_url}
onChange={(e) => setAnalysisForm(prev => ({ ...prev, website_url: e.target.value }))}
placeholder="https://example.com"
sx={{ mb: 2 }}
/>
<Typography variant="subtitle2" gutterBottom>
Competitors
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField
fullWidth
label="Add Competitor"
value={newCompetitor}
onChange={(e) => setNewCompetitor(e.target.value)}
placeholder="competitor.com"
onKeyPress={(e) => e.key === 'Enter' && handleAddCompetitor()}
/>
<Button
variant="outlined"
onClick={handleAddCompetitor}
disabled={!newCompetitor.trim()}
>
<AddIcon />
</Button>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 3 }}>
{analysisForm.competitors.map((competitor, index) => (
<Chip
key={index}
label={competitor}
onDelete={() => handleRemoveCompetitor(competitor)}
color="primary"
variant="outlined"
/>
))}
</Box>
<Typography variant="subtitle2" gutterBottom>
Keywords
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField
fullWidth
label="Add Keyword"
value={newKeyword}
onChange={(e) => setNewKeyword(e.target.value)}
placeholder="target keyword"
onKeyPress={(e) => e.key === 'Enter' && handleAddKeyword()}
/>
<Button
variant="outlined"
onClick={handleAddKeyword}
disabled={!newKeyword.trim()}
>
<AddIcon />
</Button>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 3 }}>
{analysisForm.keywords.map((keyword, index) => (
<Chip
key={index}
label={keyword}
onDelete={() => handleRemoveKeyword(keyword)}
color="secondary"
variant="outlined"
/>
))}
</Box>
<Button
variant="contained"
fullWidth
onClick={handleRunAnalysis}
disabled={loading || dataLoading || !analysisForm.website_url}
startIcon={<AssessmentIcon />}
>
{loading || dataLoading ? 'Running Analysis...' : 'Run Gap Analysis'}
</Button>
</Paper>
</Grid>
<TabPanel value={tabValue} index={0}>
<ContentOptimizerTab />
</TabPanel>
{/* Content Gaps */}
<Grid item xs={12} md={8}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<WarningIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Gaps
</Typography>
<Divider sx={{ mb: 2 }} />
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : transformedGapAnalyses.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
No previous analyses found. Run your first analysis to see results here.
</Typography>
) : (
<Grid container spacing={2}>
{transformedGapAnalyses.map((analysis) => (
<Grid item xs={12} md={6} lg={4} key={analysis.id}>
<Card>
<CardContent>
<Typography variant="h6" component="div">
{analysis.website_url}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{new Date(analysis.created_at).toLocaleDateString()}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip
label={`${analysis.competitors?.length || 0} competitors`}
size="small"
variant="outlined"
/>
<Chip
label={`${analysis.keywords?.length || 0} keywords`}
size="small"
variant="outlined"
/>
<Chip
label={`${analysis.gaps?.length || 0} gaps found`}
size="small"
color="warning"
/>
<Chip
label={`${analysis.recommendations?.length || 0} recommendations`}
size="small"
color="success"
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
</Paper>
<TabPanel value={tabValue} index={1}>
<TrendingTopicsTab />
</TabPanel>
{/* Detailed Analysis Results */}
{transformedGapAnalyses.length > 0 && (
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingUpIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Detailed Analysis Results
</Typography>
<Divider sx={{ mb: 2 }} />
{transformedGapAnalyses.map((analysis, index) => (
<Box key={index} sx={{ mb: 3 }}>
<Typography variant="subtitle1" gutterBottom>
Analysis for {analysis.website_url}
</Typography>
{analysis.gaps && analysis.gaps.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
Identified Content Gaps:
</Typography>
<List dense>
{analysis.gaps.map((gap, gapIndex) => (
<ListItem key={gapIndex}>
<ListItemIcon>
<WarningIcon color="warning" />
</ListItemIcon>
<ListItemText primary={gap} />
</ListItem>
))}
</List>
</Box>
)}
{analysis.recommendations && analysis.recommendations.length > 0 && (
<Box>
<Typography variant="subtitle2" gutterBottom>
Recommendations:
</Typography>
<List dense>
{analysis.recommendations.map((rec, recIndex) => (
<ListItem key={recIndex}>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary={rec.title || rec.description || 'Recommendation'}
secondary={rec.description}
/>
</ListItem>
))}
</List>
</Box>
)}
</Box>
))}
</Paper>
)}
</Grid>
</Grid>
<TabPanel value={tabValue} index={2}>
<KeywordResearchTab />
</TabPanel>
<TabPanel value={tabValue} index={3}>
<PerformanceAnalyticsTab />
</TabPanel>
<TabPanel value={tabValue} index={4}>
<ContentPillarsTab />
</TabPanel>
<TabPanel value={tabValue} index={5}>
<RefineAnalysisTab />
</TabPanel>
</Box>
);
};

View File

@@ -0,0 +1,189 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Grid,
Paper,
Typography,
Card,
CardContent,
Chip,
CircularProgress,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Button
} from '@mui/material';
import {
Search as SearchIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
const KeywordResearchTab: React.FC = () => {
const [keywordResearch, setKeywordResearch] = useState<any>(null);
const [dataLoading, setDataLoading] = useState(false);
useEffect(() => {
loadKeywordResearch();
}, []);
const loadKeywordResearch = async () => {
try {
setDataLoading(true);
const eventSource = await contentPlanningApi.streamKeywordResearch();
contentPlanningApi.handleSSEData(
eventSource,
(data) => {
console.log('Keyword Research SSE Data:', data);
if (data.type === 'result' && data.data) {
setKeywordResearch(data.data);
}
},
(error) => {
console.error('Keyword Research SSE Error:', error);
},
() => {
setDataLoading(false);
}
);
} catch (error) {
console.error('Error loading keyword research:', error);
setDataLoading(false);
}
};
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Keyword Research
</Typography>
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : keywordResearch && keywordResearch.trend_analysis ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
High Volume Keywords
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Volume</TableCell>
<TableCell>Difficulty</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => (
<TableRow key={index}>
<TableCell>{keyword.keyword}</TableCell>
<TableCell>{keyword.volume}</TableCell>
<TableCell>
<Chip
label={keyword.difficulty}
color={keyword.difficulty === 'Low' ? 'success' : keyword.difficulty === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Trending Keywords
</Typography>
{(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{keyword.keyword}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={keyword.growth}
color="success"
size="small"
/>
<Chip
label={keyword.opportunity}
color={keyword.opportunity === 'High' ? 'success' : 'primary'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Keyword Opportunities
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Keyword</TableCell>
<TableCell>Search Volume</TableCell>
<TableCell>Competition</TableCell>
<TableCell>CPC</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(keywordResearch.opportunities || []).map((opportunity: any, index: number) => (
<TableRow key={index}>
<TableCell>{opportunity.keyword}</TableCell>
<TableCell>{opportunity.search_volume}</TableCell>
<TableCell>
<Chip
label={opportunity.competition}
color={opportunity.competition === 'Low' ? 'success' : opportunity.competition === 'Medium' ? 'warning' : 'error'}
size="small"
/>
</TableCell>
<TableCell>${opportunity.cpc}</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Add to Strategy
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No keyword research data available
</Typography>
)}
</Box>
);
};
export default KeywordResearchTab;

View File

@@ -0,0 +1,60 @@
import React from 'react';
import {
Box,
Grid,
Card,
CardContent,
Typography
} from '@mui/material';
import {
BarChart as BarChartIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
const PerformanceAnalyticsTab: React.FC = () => {
const { performanceMetrics } = useContentPlanningStore();
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Performance Analytics
</Typography>
{performanceMetrics ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Content Performance by Type
</Typography>
<Typography variant="body2" color="text.secondary">
No content performance data available
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Growth Trends
</Typography>
<Typography variant="body2" color="text.secondary">
No trend data available
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No performance analytics data available
</Typography>
)}
</Box>
);
};
export default PerformanceAnalyticsTab;

View File

@@ -0,0 +1,406 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Grid,
Paper,
Typography,
Button,
TextField,
Card,
CardContent,
Chip,
Divider,
Alert,
CircularProgress,
List,
ListItem,
ListItemText,
ListItemIcon
} from '@mui/material';
import {
Search as SearchIcon,
Add as AddIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
Assessment as AssessmentIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
const RefineAnalysisTab: React.FC = () => {
const {
gapAnalyses,
loading,
error,
loadGapAnalyses,
analyzeContentGaps,
updateGapAnalyses
} = useContentPlanningStore();
const [analysisForm, setAnalysisForm] = useState({
website_url: '',
competitors: [] as string[],
keywords: [] as string[]
});
const [newCompetitor, setNewCompetitor] = useState('');
const [newKeyword, setNewKeyword] = useState('');
const [dataLoading, setDataLoading] = useState(false);
useEffect(() => {
loadGapAnalysisData();
}, []);
const loadGapAnalysisData = async () => {
try {
setDataLoading(true);
const response = await contentPlanningApi.getGapAnalysesSafe();
console.log('Gap Analysis Response:', response);
// Transform the backend response to match frontend expectations
if (response && response.gap_analyses) {
const transformedAnalyses = response.gap_analyses.map((analysis: any, index: number) => ({
id: analysis.id || `analysis_${index}`,
website_url: analysis.website_url || 'example.com',
competitors: analysis.competitors || [],
keywords: analysis.keywords || [],
gaps: analysis.gaps || [],
recommendations: analysis.recommendations || [],
created_at: analysis.created_at || new Date().toISOString()
}));
console.log('Transformed Analyses:', transformedAnalyses);
// Update the store with transformed data
updateGapAnalyses(transformedAnalyses);
} else {
console.log('No gap analyses found in response');
updateGapAnalyses([]);
}
} catch (error) {
console.error('Error loading gap analysis data:', error);
updateGapAnalyses([]);
} finally {
setDataLoading(false);
}
};
const handleAddCompetitor = () => {
if (newCompetitor.trim() && !analysisForm.competitors.includes(newCompetitor.trim())) {
setAnalysisForm(prev => ({
...prev,
competitors: [...prev.competitors, newCompetitor.trim()]
}));
setNewCompetitor('');
}
};
const handleRemoveCompetitor = (competitorToRemove: string) => {
setAnalysisForm(prev => ({
...prev,
competitors: prev.competitors.filter(comp => comp !== competitorToRemove)
}));
};
const handleAddKeyword = () => {
if (newKeyword.trim() && !analysisForm.keywords.includes(newKeyword.trim())) {
setAnalysisForm(prev => ({
...prev,
keywords: [...prev.keywords, newKeyword.trim()]
}));
setNewKeyword('');
}
};
const handleRemoveKeyword = (keywordToRemove: string) => {
setAnalysisForm(prev => ({
...prev,
keywords: prev.keywords.filter(keyword => keyword !== keywordToRemove)
}));
};
const handleRunAnalysis = async () => {
if (!analysisForm.website_url) {
return;
}
try {
setDataLoading(true);
await analyzeContentGaps({
website_url: analysisForm.website_url,
competitors: analysisForm.competitors,
keywords: analysisForm.keywords
});
// Reload data after analysis
await loadGapAnalyses();
// Reset form
setAnalysisForm({
website_url: '',
competitors: [],
keywords: []
});
} catch (error) {
console.error('Error running gap analysis:', error);
} finally {
setDataLoading(false);
}
};
// Ensure gapAnalyses is always an array and transform the data structure
const safeGapAnalyses = Array.isArray(gapAnalyses) ? gapAnalyses : [];
// Transform backend data structure to frontend expected structure
const transformedGapAnalyses = safeGapAnalyses.map((analysis, index) => {
// Handle the actual backend structure: { recommendations: [...] }
const recommendations = analysis.recommendations || [];
return {
id: analysis.id || `analysis-${index}`,
website_url: analysis.website_url || 'Unknown Website',
competitors: analysis.competitors || [],
keywords: analysis.keywords || [],
recommendations: recommendations,
created_at: analysis.created_at || new Date().toISOString(),
// Extract gaps from recommendations if available
gaps: recommendations.length > 0 ?
recommendations.filter((rec: any) => rec.type === 'gap').map((rec: any) => rec.title || rec.description || 'Content gap identified') :
[]
};
});
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Refine Analysis
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Grid container spacing={3}>
{/* Analysis Setup */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<SearchIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Analysis Setup
</Typography>
<Divider sx={{ mb: 2 }} />
<TextField
fullWidth
label="Website URL"
value={analysisForm.website_url}
onChange={(e) => setAnalysisForm(prev => ({ ...prev, website_url: e.target.value }))}
placeholder="https://example.com"
sx={{ mb: 2 }}
/>
<Typography variant="subtitle2" gutterBottom>
Competitors
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField
fullWidth
label="Add Competitor"
value={newCompetitor}
onChange={(e) => setNewCompetitor(e.target.value)}
placeholder="competitor.com"
onKeyPress={(e) => e.key === 'Enter' && handleAddCompetitor()}
/>
<Button
variant="outlined"
onClick={handleAddCompetitor}
disabled={!newCompetitor.trim()}
>
<AddIcon />
</Button>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 3 }}>
{analysisForm.competitors.map((competitor, index) => (
<Chip
key={index}
label={competitor}
onDelete={() => handleRemoveCompetitor(competitor)}
color="primary"
variant="outlined"
/>
))}
</Box>
<Typography variant="subtitle2" gutterBottom>
Keywords
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField
fullWidth
label="Add Keyword"
value={newKeyword}
onChange={(e) => setNewKeyword(e.target.value)}
placeholder="target keyword"
onKeyPress={(e) => e.key === 'Enter' && handleAddKeyword()}
/>
<Button
variant="outlined"
onClick={handleAddKeyword}
disabled={!newKeyword.trim()}
>
<AddIcon />
</Button>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 3 }}>
{analysisForm.keywords.map((keyword, index) => (
<Chip
key={index}
label={keyword}
onDelete={() => handleRemoveKeyword(keyword)}
color="secondary"
variant="outlined"
/>
))}
</Box>
<Button
variant="contained"
fullWidth
onClick={handleRunAnalysis}
disabled={loading || dataLoading || !analysisForm.website_url}
startIcon={<AssessmentIcon />}
>
{loading || dataLoading ? 'Running Analysis...' : 'Run Gap Analysis'}
</Button>
</Paper>
</Grid>
{/* Content Gaps */}
<Grid item xs={12} md={8}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<WarningIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Gaps
</Typography>
<Divider sx={{ mb: 2 }} />
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : transformedGapAnalyses.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
No previous analyses found. Run your first analysis to see results here.
</Typography>
) : (
<Grid container spacing={2}>
{transformedGapAnalyses.map((analysis) => (
<Grid item xs={12} md={6} lg={4} key={analysis.id}>
<Card>
<CardContent>
<Typography variant="h6" component="div">
{analysis.website_url}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{new Date(analysis.created_at).toLocaleDateString()}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip
label={`${analysis.competitors?.length || 0} competitors`}
size="small"
variant="outlined"
/>
<Chip
label={`${analysis.keywords?.length || 0} keywords`}
size="small"
variant="outlined"
/>
<Chip
label={`${analysis.gaps?.length || 0} gaps found`}
size="small"
color="warning"
/>
<Chip
label={`${analysis.recommendations?.length || 0} recommendations`}
size="small"
color="success"
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
</Paper>
{/* Detailed Analysis Results */}
{transformedGapAnalyses.length > 0 && (
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingUpIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Detailed Analysis Results
</Typography>
<Divider sx={{ mb: 2 }} />
{transformedGapAnalyses.map((analysis, index) => (
<Box key={index} sx={{ mb: 3 }}>
<Typography variant="subtitle1" gutterBottom>
Analysis for {analysis.website_url}
</Typography>
{analysis.gaps && analysis.gaps.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
Identified Content Gaps:
</Typography>
<List dense>
{analysis.gaps.map((gap, gapIndex) => (
<ListItem key={gapIndex}>
<ListItemIcon>
<WarningIcon color="warning" />
</ListItemIcon>
<ListItemText primary={gap} />
</ListItem>
))}
</List>
</Box>
)}
{analysis.recommendations && analysis.recommendations.length > 0 && (
<Box>
<Typography variant="subtitle2" gutterBottom>
Recommendations:
</Typography>
<List dense>
{analysis.recommendations.map((rec, recIndex) => (
<ListItem key={recIndex}>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary={rec.title || rec.description || 'Recommendation'}
secondary={rec.description}
/>
</ListItem>
))}
</List>
</Box>
)}
</Box>
))}
</Paper>
)}
</Grid>
</Grid>
</Box>
);
};
export default RefineAnalysisTab;

View File

@@ -0,0 +1,65 @@
import React from 'react';
import {
Box,
Grid,
Paper,
Typography,
Chip
} from '@mui/material';
import {
TrendingUp as TrendingIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
const TrendingTopicsTab: React.FC = () => {
const { trendingTopics } = useContentPlanningStore();
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Trending Topics
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Trending Topics
</Typography>
{trendingTopics ? (
<Box>
<Typography variant="body1" gutterBottom>
Current Trending Topics
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{trendingTopics.trending_topics?.map((topic: any, index: number) => (
<Chip
key={index}
label={topic.name || topic.keyword}
color="primary"
variant="outlined"
/>
))}
</Box>
</Box>
) : (
<Box sx={{ textAlign: 'center', py: 4 }}>
<TrendingIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" color="text.secondary" gutterBottom>
No trending topics
</Typography>
<Typography variant="body2" color="text.secondary">
Get trending topics for your industry
</Typography>
</Box>
)}
</Paper>
</Grid>
</Grid>
</Box>
);
};
export default TrendingTopicsTab;