Added image generation to blog writer

This commit is contained in:
ajaysi
2025-10-31 15:59:16 +05:30
parent 3219e6bbe4
commit cdb41aec1b
80 changed files with 7662 additions and 3951 deletions

View File

@@ -1,6 +1,6 @@
/**
* Keyword Analysis Component
*
*
* Displays comprehensive keyword analysis including keyword types, densities,
* missing keywords, over-optimization, and distribution analysis.
*/
@@ -15,7 +15,7 @@ import {
IconButton,
Tooltip
} from '@mui/material';
import {
import {
GpsFixed,
Search,
Warning
@@ -36,86 +36,140 @@ interface KeywordAnalysisProps {
};
}
const baseCardSx = {
p: 3,
backgroundColor: '#ffffff',
border: '1px solid #e2e8f0',
borderRadius: 2,
boxShadow: '0 12px 28px rgba(15,23,42,0.08)',
color: '#0f172a',
minHeight: '100%'
} as const;
const subCard = (color: string) => ({
p: 2,
borderRadius: 2,
border: `1px solid ${color}`,
background: `linear-gradient(145deg, ${color}14, ${color}1f)`
});
export const KeywordAnalysis: React.FC<KeywordAnalysisProps> = ({ detailedAnalysis }) => {
const keywordData = detailedAnalysis?.keyword_analysis;
const renderDensityRow = (keyword: string, density: number) => {
const status = density > 3 ? 'Over-optimized' : density < 1 ? 'Under-optimized' : 'Optimal';
const chipColor = density > 3 ? 'error' : density < 1 ? 'warning' : 'success';
return (
<Box
key={keyword}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.75rem 1rem',
borderRadius: 2,
backgroundColor: '#f1f5f9'
}}
>
<Typography variant="body2" sx={{ fontWeight: 600, color: '#334155' }}>
{keyword}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="caption" sx={{ color: '#64748b' }}>
{status}
</Typography>
<Chip label={`${density.toFixed(1)}%`} color={chipColor} size="small" sx={{ fontWeight: 600 }} />
</Box>
</Box>
);
};
return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<GpsFixed sx={{ color: 'primary.main' }} />
<Typography variant="h6" component="h3" sx={{ fontWeight: 600 }}>
<Typography variant="h6" component="h3" sx={{ fontWeight: 700, letterSpacing: 0.2, color: '#0f172a' }}>
Keyword Analysis
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* Keyword Types Overview */}
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={baseCardSx}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#0f172a', mb: 2 }}>
Keyword Types Found
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, background: 'rgba(76, 175, 80, 0.1)', borderRadius: 2, border: '1px solid rgba(76, 175, 80, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'success.main', mb: 1 }}>
<Box sx={subCard('rgba(34,197,94,0.5)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#16a34a', mb: 1 }}>
Primary Keywords
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
{detailedAnalysis?.keyword_analysis?.primary_keywords?.length || 0} found
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
{keywordData?.primary_keywords?.length || 0} found
</Typography>
{detailedAnalysis?.keyword_analysis?.primary_keywords?.slice(0, 3).map((keyword: string) => (
<Chip key={keyword} label={keyword} size="small" sx={{ mr: 0.5, mb: 0.5 }} />
))}
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{keywordData?.primary_keywords?.slice(0, 3).map((keyword) => (
<Chip key={keyword} label={keyword} size="small" sx={{ fontWeight: 600 }} />
))}
</Box>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, background: 'rgba(33, 150, 243, 0.1)', borderRadius: 2, border: '1px solid rgba(33, 150, 243, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'primary.main', mb: 1 }}>
<Box sx={subCard('rgba(59,130,246,0.5)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#2563eb', mb: 1 }}>
Long-tail Keywords
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
{detailedAnalysis?.keyword_analysis?.long_tail_keywords?.length || 0} found
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
{keywordData?.long_tail_keywords?.length || 0} found
</Typography>
{detailedAnalysis?.keyword_analysis?.long_tail_keywords?.slice(0, 2).map((keyword: string) => (
<Chip key={keyword} label={keyword} size="small" variant="outlined" sx={{ mr: 0.5, mb: 0.5 }} />
))}
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{keywordData?.long_tail_keywords?.slice(0, 3).map((keyword) => (
<Chip key={keyword} label={keyword} variant="outlined" size="small" sx={{ fontWeight: 600, borderColor: '#93c5fd', color: '#1d4ed8' }} />
))}
</Box>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, background: 'rgba(156, 39, 176, 0.1)', borderRadius: 2, border: '1px solid rgba(156, 39, 176, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'secondary.main', mb: 1 }}>
<Box sx={subCard('rgba(168,85,247,0.5)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#9333ea', mb: 1 }}>
Semantic Keywords
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
{detailedAnalysis?.keyword_analysis?.semantic_keywords?.length || 0} found
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
{keywordData?.semantic_keywords?.length || 0} found
</Typography>
{detailedAnalysis?.keyword_analysis?.semantic_keywords?.slice(0, 2).map((keyword: string) => (
<Chip key={keyword} label={keyword} size="small" variant="outlined" color="secondary" sx={{ mr: 0.5, mb: 0.5 }} />
))}
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{keywordData?.semantic_keywords?.slice(0, 3).map((keyword) => (
<Chip key={keyword} label={keyword} variant="outlined" color="secondary" size="small" sx={{ fontWeight: 600, borderColor: '#d8b4fe' }} />
))}
</Box>
</Box>
</Grid>
</Grid>
</Paper>
{/* Keyword Densities */}
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={baseCardSx}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#0f172a' }}>
Keyword Densities
</Typography>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
Keyword Density Analysis
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
Shows how frequently each keyword appears in your content as a percentage of total words.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, color: '#64748b' }}>
<strong>Optimal Range:</strong> 1-3% for primary keywords
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, color: '#64748b' }}>
<strong>Too Low (&lt;1%):</strong> Keyword may not be prominent enough
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Too High (&gt;3%):</strong> Risk of keyword stuffing
</Typography>
</Box>
@@ -123,108 +177,96 @@ export const KeywordAnalysis: React.FC<KeywordAnalysisProps> = ({ detailedAnalys
arrow
>
<IconButton size="small" sx={{ color: 'primary.main' }}>
<Search />
<Search fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{detailedAnalysis?.keyword_analysis?.keyword_density && Object.keys(detailedAnalysis.keyword_analysis.keyword_density).length > 0 ? (
Object.entries(detailedAnalysis.keyword_analysis.keyword_density).map(([keyword, density]) => (
<Box key={keyword} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', p: 1, borderRadius: 1, background: 'rgba(0,0,0,0.02)' }}>
<Typography variant="body2" sx={{ fontWeight: 500 }}>{keyword}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{density > 3 ? 'Over-optimized' : density < 1 ? 'Under-optimized' : 'Optimal'}
</Typography>
<Chip
label={`${density.toFixed(1)}%`}
color={density > 3 ? 'error' : density < 1 ? 'warning' : 'success'}
size="small"
/>
</Box>
</Box>
))
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.2 }}>
{keywordData?.keyword_density && Object.keys(keywordData.keyword_density).length > 0 ? (
Object.entries(keywordData.keyword_density).map(([keyword, density]) => renderDensityRow(keyword, density))
) : (
<Typography variant="body2" sx={{ color: 'text.secondary', fontStyle: 'italic' }}>
<Typography variant="body2" sx={{ color: '#64748b', fontStyle: 'italic' }}>
No keyword density data available. Make sure your research data includes target keywords.
</Typography>
)}
</Box>
</Paper>
{/* Missing Keywords */}
{detailedAnalysis?.keyword_analysis?.missing_keywords && detailedAnalysis.keyword_analysis.missing_keywords.length > 0 && (
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
{keywordData?.missing_keywords && keywordData.missing_keywords.length > 0 && (
<Paper sx={baseCardSx}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, color: 'error.main' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#dc2626' }}>
Missing Keywords
</Typography>
<Tooltip
title="Keywords from your research that are not found in the content. Consider adding these to improve SEO."
arrow
>
<IconButton size="small" sx={{ color: 'error.main' }}>
<Warning />
<Tooltip title="Keywords from your research that are not found in the content. Consider adding these to improve SEO." arrow>
<IconButton size="small" sx={{ color: '#dc2626' }}>
<Warning fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{detailedAnalysis.keyword_analysis.missing_keywords.map((keyword: string) => (
<Chip key={keyword} label={keyword} color="error" variant="outlined" />
{keywordData.missing_keywords.map((keyword) => (
<Chip key={keyword} label={keyword} variant="outlined" size="small" sx={{ borderColor: '#fecaca', color: '#b91c1c', fontWeight: 600 }} />
))}
</Box>
</Paper>
)}
{/* Over-Optimized Keywords */}
{detailedAnalysis?.keyword_analysis?.over_optimization && detailedAnalysis.keyword_analysis.over_optimization.length > 0 && (
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
{keywordData?.over_optimization && keywordData.over_optimization.length > 0 && (
<Paper sx={baseCardSx}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, color: 'warning.main' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#d97706' }}>
Over-Optimized Keywords
</Typography>
<Tooltip
title="Keywords that appear too frequently (over 3% density). Consider reducing their usage to avoid keyword stuffing penalties."
arrow
>
<IconButton size="small" sx={{ color: 'warning.main' }}>
<Warning />
<Tooltip title="Keywords that appear too frequently (over 3% density). Consider reducing their usage." arrow>
<IconButton size="small" sx={{ color: '#d97706' }}>
<Warning fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{detailedAnalysis.keyword_analysis.over_optimization.map((keyword: string) => (
<Chip key={keyword} label={keyword} color="warning" variant="outlined" />
{keywordData.over_optimization.map((keyword) => (
<Chip key={keyword} label={keyword} variant="outlined" size="small" sx={{ borderColor: '#fcd34d', color: '#b45309', fontWeight: 600 }} />
))}
</Box>
</Paper>
)}
{/* Keyword Distribution Analysis */}
{detailedAnalysis?.keyword_analysis?.keyword_distribution && Object.keys(detailedAnalysis.keyword_analysis.keyword_distribution).length > 0 && (
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
{keywordData?.keyword_distribution && Object.keys(keywordData.keyword_distribution).length > 0 && (
<Paper sx={baseCardSx}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#0f172a', mb: 2 }}>
Keyword Distribution Analysis
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{Object.entries(detailedAnalysis.keyword_analysis.keyword_distribution).map(([keyword, data]: [string, any]) => (
<Box key={keyword} sx={{ p: 2, background: 'rgba(0,0,0,0.02)', borderRadius: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
"{keyword}"
{Object.entries(keywordData.keyword_distribution).map(([keyword, data]: [string, any]) => (
<Box
key={keyword}
sx={{
p: 2,
borderRadius: 2,
border: '1px solid #e2e8f0',
backgroundColor: '#f8fafc'
}}
>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#0f172a', mb: 1 }}>
{keyword}
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
<Typography variant="caption" sx={{ color: '#64748b' }}>
Density: {data.density?.toFixed(1)}%
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
<Typography variant="caption" sx={{ color: '#64748b' }}>
In Headings: {data.in_headings ? 'Yes' : 'No'}
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
<Typography variant="caption" sx={{ color: '#64748b' }}>
First Occurrence: Character {data.first_occurrence || 'Not found'}
</Typography>
</Grid>

View File

@@ -75,9 +75,22 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
return `${current}/${max}`;
};
// Consistent text input styling for better contrast
const textInputSx = {
'& .MuiInputBase-input': {
color: '#202124'
},
'& .MuiInputLabel-root': {
color: '#5f6368'
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#dadce0'
}
} as const;
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1, color: '#202124', fontWeight: 600 }}>
<SearchIcon sx={{ color: 'primary.main' }} />
Core SEO Metadata
</Typography>
@@ -85,10 +98,10 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
<Grid container spacing={3}>
{/* SEO Title */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<SearchIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<SearchIcon sx={{ fontSize: 20, color: '#5f6368' }} />
SEO Title
</Typography>
<Tooltip title="Copy to clipboard">
@@ -107,6 +120,7 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
value={metadata.seo_title || ''}
onChange={handleTextFieldChange('seo_title')}
placeholder="Enter SEO-optimized title (50-60 characters)"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -120,18 +134,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
)
}}
/>
<Alert severity="info" sx={{ mt: 1 }}>
Include your primary keyword and make it compelling for clicks
</Alert>
<Typography variant="caption" sx={{ mt: 1, color: '#5f6368', display: 'block' }}>
Include your primary keyword and keep between 5060 characters
</Typography>
</Paper>
</Grid>
{/* Meta Description */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<SearchIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<SearchIcon sx={{ fontSize: 20, color: '#5f6368' }} />
Meta Description
</Typography>
<Tooltip title="Copy to clipboard">
@@ -150,6 +164,7 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
value={metadata.meta_description || ''}
onChange={handleTextFieldChange('meta_description')}
placeholder="Enter compelling meta description (150-160 characters)"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -163,18 +178,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
)
}}
/>
<Alert severity="info" sx={{ mt: 1 }}>
Include a call-to-action and your primary keyword
</Alert>
<Typography variant="caption" sx={{ mt: 1, color: '#5f6368', display: 'block' }}>
Aim for 150160 characters with a clear value proposition
</Typography>
</Paper>
</Grid>
{/* URL Slug */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<LinkIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<LinkIcon sx={{ fontSize: 20, color: '#5f6368' }} />
URL Slug
</Typography>
<Tooltip title="Copy to clipboard">
@@ -192,16 +207,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
onChange={handleTextFieldChange('url_slug')}
placeholder="seo-friendly-url-slug"
helperText="Use lowercase letters, numbers, and hyphens only"
sx={textInputSx}
FormHelperTextProps={{ sx: { color: '#5f6368' } }}
/>
</Paper>
</Grid>
{/* Focus Keyword */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<TrendingUpIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<TrendingUpIcon sx={{ fontSize: 20, color: '#5f6368' }} />
Focus Keyword
</Typography>
<Tooltip title="Copy to clipboard">
@@ -219,16 +236,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
onChange={handleTextFieldChange('focus_keyword')}
placeholder="primary-keyword"
helperText="Your main SEO keyword for this post"
sx={textInputSx}
FormHelperTextProps={{ sx: { color: '#5f6368' } }}
/>
</Paper>
</Grid>
{/* Blog Tags */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<TagIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<TagIcon sx={{ fontSize: 20, color: '#5f6368' }} />
Blog Tags
</Typography>
<Tooltip title="Copy to clipboard">
@@ -241,12 +260,12 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
</Tooltip>
</Box>
<FormControl fullWidth>
<InputLabel>Tags</InputLabel>
<InputLabel sx={{ color: '#5f6368' }}>Tags</InputLabel>
<Select
multiple
value={metadata.blog_tags || []}
onChange={handleTagsChange('blog_tags')}
input={<OutlinedInput label="Tags" />}
input={<OutlinedInput label="Tags" sx={{ color: '#202124', '& .MuiOutlinedInput-notchedOutline': { borderColor: '#dadce0' } }} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
@@ -262,18 +281,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
))}
</Select>
</FormControl>
<Alert severity="info" sx={{ mt: 1 }}>
Add relevant tags for better categorization and discoverability
</Alert>
<Typography variant="caption" sx={{ mt: 1, color: '#5f6368', display: 'block' }}>
Add 36 relevant tags for better categorization and discoverability
</Typography>
</Paper>
</Grid>
{/* Blog Categories */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<CategoryIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<CategoryIcon sx={{ fontSize: 20, color: '#5f6368' }} />
Blog Categories
</Typography>
<Tooltip title="Copy to clipboard">
@@ -286,12 +305,12 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
</Tooltip>
</Box>
<FormControl fullWidth>
<InputLabel>Categories</InputLabel>
<InputLabel sx={{ color: '#5f6368' }}>Categories</InputLabel>
<Select
multiple
value={metadata.blog_categories || []}
onChange={handleTagsChange('blog_categories')}
input={<OutlinedInput label="Categories" />}
input={<OutlinedInput label="Categories" sx={{ color: '#202124', '& .MuiOutlinedInput-notchedOutline': { borderColor: '#dadce0' } }} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
@@ -307,18 +326,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
))}
</Select>
</FormControl>
<Alert severity="info" sx={{ mt: 1 }}>
Select 2-3 primary categories for your content
</Alert>
<Typography variant="caption" sx={{ mt: 1, color: '#5f6368', display: 'block' }}>
Select 13 primary categories for your content
</Typography>
</Paper>
</Grid>
{/* Social Hashtags */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<TagIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<TagIcon sx={{ fontSize: 20, color: '#5f6368' }} />
Social Hashtags
</Typography>
<Tooltip title="Copy to clipboard">
@@ -331,12 +350,12 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
</Tooltip>
</Box>
<FormControl fullWidth>
<InputLabel>Hashtags</InputLabel>
<InputLabel sx={{ color: '#5f6368' }}>Hashtags</InputLabel>
<Select
multiple
value={metadata.social_hashtags || []}
onChange={handleTagsChange('social_hashtags')}
input={<OutlinedInput label="Hashtags" />}
input={<OutlinedInput label="Hashtags" sx={{ color: '#202124', '& .MuiOutlinedInput-notchedOutline': { borderColor: '#dadce0' } }} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
@@ -352,18 +371,18 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
))}
</Select>
</FormControl>
<Alert severity="info" sx={{ mt: 1 }}>
Include # symbol for social media platforms
</Alert>
<Typography variant="caption" sx={{ mt: 1, color: '#5f6368', display: 'block' }}>
Include # symbol (e.g., #multimodalAI). 35 hashtags recommended.
</Typography>
</Paper>
</Grid>
{/* Reading Time */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1 }}>
<ScheduleIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<ScheduleIcon sx={{ fontSize: 20, color: '#5f6368' }} />
Reading Time
</Typography>
<Tooltip title="Copy to clipboard">
@@ -385,6 +404,8 @@ export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
endAdornment: <InputAdornment position="end">minutes</InputAdornment>
}}
helperText="Estimated reading time for your content"
sx={textInputSx}
FormHelperTextProps={{ sx: { color: '#5f6368' } }}
/>
</Paper>
</Grid>

View File

@@ -12,28 +12,35 @@ import {
Box,
Typography,
Paper,
Grid,
Card,
CardContent,
Chip,
Alert
Tabs,
Tab,
Tooltip,
IconButton
} from '@mui/material';
import {
Search as SearchIcon,
Code as CodeIcon,
Facebook as FacebookIcon,
Twitter as TwitterIcon,
Google as GoogleIcon
Google as GoogleIcon,
Info as InfoIcon
} from '@mui/icons-material';
interface PreviewCardProps {
metadata: any;
blogTitle: string;
previewTabValue: string;
onPreviewTabChange: (value: string) => void;
}
export const PreviewCard: React.FC<PreviewCardProps> = ({
metadata,
blogTitle
blogTitle,
previewTabValue,
onPreviewTabChange
}) => {
const getCurrentDate = () => {
return new Date().toLocaleDateString('en-US', {
@@ -45,320 +52,491 @@ export const PreviewCard: React.FC<PreviewCardProps> = ({
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
{/* Title with Tooltip */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<SearchIcon sx={{ color: 'primary.main' }} />
Live Preview
</Typography>
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Live Preview
</Typography>
<Tooltip
title="This is how your blog post will appear in search results and social media platforms"
arrow
placement="top"
>
<IconButton size="small" sx={{ color: 'text.secondary' }}>
<InfoIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<Grid container spacing={3}>
{/* Google Search Results Preview */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<GoogleIcon sx={{ color: '#4285F4' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Google Search Results
{/* Platform Sub-Tabs */}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs
value={previewTabValue}
onChange={(e, newValue) => onPreviewTabChange(newValue)}
variant="scrollable"
scrollButtons="auto"
sx={{
'& .MuiTab-root': {
textTransform: 'none',
fontWeight: 500,
minHeight: 48
},
'& .Mui-selected': {
fontWeight: 600
}
}}
>
<Tab
icon={<GoogleIcon />}
iconPosition="start"
label="Google Search Results"
value="google"
/>
<Tab
icon={<FacebookIcon />}
iconPosition="start"
label="Facebook Preview"
value="facebook"
/>
<Tab
icon={<TwitterIcon />}
iconPosition="start"
label="Twitter Preview"
value="twitter"
/>
<Tab
icon={<CodeIcon />}
iconPosition="start"
label="Rich Snippets Preview"
value="richsnippets"
/>
</Tabs>
</Box>
{/* Google Search Results Preview */}
{previewTabValue === 'google' && (
<Paper
sx={{
p: 3,
background: '#ffffff',
border: '1px solid #e0e0e0',
borderRadius: 2,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<GoogleIcon sx={{ color: '#4285F4', fontSize: 28 }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: '#202124' }}>
Google Search Results
</Typography>
</Box>
{/* Google SERP Preview - Light Theme (matches actual Google) */}
<Card
sx={{
background: '#ffffff',
border: 'none',
boxShadow: 'none',
maxWidth: 600
}}
>
<CardContent sx={{ p: 2.5 }}>
{/* URL - Google Blue */}
<Typography
variant="caption"
sx={{
color: '#202124',
fontSize: '14px',
lineHeight: 1.3,
mb: 0.5,
display: 'block',
fontFamily: 'arial, sans-serif'
}}
>
{metadata.canonical_url || 'https://yourwebsite.com/blog-post'}
</Typography>
<Chip label="SERP Preview" size="small" color="primary" />
</Box>
{/* Title - Google Blue, hover underline */}
<Typography
variant="h6"
sx={{
color: '#1a0dab',
fontWeight: 400,
fontSize: '20px',
lineHeight: 1.3,
mb: 0.5,
cursor: 'pointer',
fontFamily: 'arial, sans-serif',
'&:hover': { textDecoration: 'underline' }
}}
>
{metadata.seo_title || blogTitle}
</Typography>
{/* Description - Google Gray */}
<Typography
variant="body2"
sx={{
color: '#4d5156',
lineHeight: 1.58,
fontSize: '14px',
fontFamily: 'arial, sans-serif',
mb: 1
}}
>
{metadata.meta_description || 'Your meta description will appear here in Google search results...'}
</Typography>
{/* Additional Info */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap', mt: 1 }}>
<Typography
variant="caption"
sx={{
color: '#70757a',
fontSize: '14px',
fontFamily: 'arial, sans-serif'
}}
>
{getCurrentDate()}
</Typography>
<Typography variant="caption" sx={{ color: '#70757a', fontSize: '14px' }}>
{metadata.reading_time || 5} min read
</Typography>
{metadata.blog_tags && metadata.blog_tags.length > 0 && (
<>
<Typography variant="caption" sx={{ color: '#70757a', fontSize: '14px' }}>
{metadata.blog_tags.slice(0, 2).join(', ')}
</Typography>
</>
)}
</Box>
</CardContent>
</Card>
</Paper>
)}
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none' }}>
<CardContent sx={{ p: 2 }}>
{/* Facebook Preview */}
{previewTabValue === 'facebook' && (
<Paper
sx={{
p: 3,
background: '#ffffff',
border: '1px solid #e0e0e0',
borderRadius: 2,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<FacebookIcon sx={{ color: '#1877F2', fontSize: 28 }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1c1e21' }}>
Facebook Preview
</Typography>
<Chip label="Open Graph" size="small" sx={{ bgcolor: '#e7f3ff', color: '#1877F2' }} />
</Box>
{/* Facebook Card Preview */}
<Card
sx={{
border: '1px solid #dadde1',
borderRadius: 2,
boxShadow: 'none',
maxWidth: 500,
background: '#ffffff',
overflow: 'hidden'
}}
>
<CardContent sx={{ p: 0 }}>
{/* Image placeholder */}
<Box sx={{
height: 262,
bgcolor: '#f2f3f5',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderBottom: '1px solid #dadde1'
}}>
{metadata.open_graph?.image ? (
<Typography variant="caption" sx={{ color: '#65676b' }}>
Image loaded
</Typography>
) : (
<Typography variant="caption" sx={{ color: '#65676b' }}>
No image set
</Typography>
)}
</Box>
<Box sx={{ p: 2.5, bgcolor: '#ffffff' }}>
{/* URL */}
<Typography variant="caption" sx={{ color: '#1a0dab', mb: 1, display: 'block' }}>
{metadata.canonical_url || 'https://yourwebsite.com/blog-post'}
<Typography
variant="caption"
sx={{
color: '#65676b',
fontSize: '12px',
mb: 0.75,
display: 'block',
textTransform: 'uppercase',
letterSpacing: '0.5px'
}}
>
{metadata.canonical_url ? new URL(metadata.canonical_url).hostname : 'yourwebsite.com'}
</Typography>
{/* Title */}
<Typography
variant="h6"
variant="subtitle1"
sx={{
color: '#1a0dab',
fontWeight: 400,
fontSize: '1.1rem',
lineHeight: 1.3,
mb: 1,
cursor: 'pointer',
'&:hover': { textDecoration: 'underline' }
fontWeight: 600,
mb: 1,
lineHeight: 1.33,
fontSize: '17px',
color: '#050505',
fontFamily: 'Helvetica, Arial, sans-serif'
}}
>
{metadata.seo_title || blogTitle}
{metadata.open_graph?.title || metadata.seo_title || blogTitle}
</Typography>
{/* Description */}
<Typography variant="body2" sx={{ color: '#4d5156', lineHeight: 1.4, mb: 1 }}>
{metadata.meta_description || 'Your meta description will appear here in Google search results...'}
<Typography
variant="body2"
sx={{
color: '#65676b',
lineHeight: 1.33,
fontSize: '15px',
fontFamily: 'Helvetica, Arial, sans-serif'
}}
>
{metadata.open_graph?.description || metadata.meta_description || 'Your description will appear here...'}
</Typography>
</Box>
</CardContent>
</Card>
</Paper>
)}
{/* Twitter Preview */}
{previewTabValue === 'twitter' && (
<Paper
sx={{
p: 3,
background: '#ffffff',
border: '1px solid #e0e0e0',
borderRadius: 2,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<TwitterIcon sx={{ color: '#1DA1F2', fontSize: 28 }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: '#0f1419' }}>
Twitter Preview
</Typography>
<Chip label="Twitter Card" size="small" sx={{ bgcolor: '#e1f5fe', color: '#1DA1F2' }} />
</Box>
{/* Twitter Card Preview */}
<Card
sx={{
border: '1px solid #eff3f4',
borderRadius: 2,
boxShadow: 'none',
maxWidth: 500,
background: '#ffffff',
overflow: 'hidden'
}}
>
<CardContent sx={{ p: 0 }}>
{/* Image placeholder */}
<Box sx={{
height: 262,
bgcolor: '#f7f9fa',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderBottom: '1px solid #eff3f4'
}}>
{metadata.twitter_card?.image ? (
<Typography variant="caption" sx={{ color: '#536471' }}>
Image loaded
</Typography>
) : (
<Typography variant="caption" sx={{ color: '#536471' }}>
No image set
</Typography>
)}
</Box>
<Box sx={{ p: 2.5, bgcolor: '#ffffff' }}>
{/* URL */}
<Typography
variant="caption"
sx={{
color: '#536471',
fontSize: '13px',
mb: 0.75,
display: 'block',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
}}
>
{metadata.canonical_url ? new URL(metadata.canonical_url).hostname : 'yourwebsite.com'}
</Typography>
{/* Additional Info */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
{getCurrentDate()}
</Typography>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
</Typography>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
{metadata.reading_time || 5} min read
</Typography>
{metadata.blog_tags && metadata.blog_tags.length > 0 && (
<>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
</Typography>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
{metadata.blog_tags.slice(0, 2).join(', ')}
</Typography>
</>
)}
</Box>
</CardContent>
</Card>
<Alert severity="info" sx={{ mt: 2 }}>
This is how your blog post will appear in Google search results
</Alert>
</Paper>
</Grid>
{/* Social Media Previews */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<FacebookIcon sx={{ color: '#1877F2' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Facebook Preview
</Typography>
<Chip label="Open Graph" size="small" color="primary" />
</Box>
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none', maxWidth: 400 }}>
<CardContent sx={{ p: 0 }}>
{/* Image placeholder */}
<Box sx={{
height: 200,
bgcolor: '#f5f5f5',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderBottom: '1px solid #e0e0e0'
}}>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{metadata.open_graph?.image ? 'Image loaded' : 'No image set'}
</Typography>
</Box>
<Box sx={{ p: 2 }}>
{/* URL */}
<Typography variant="caption" sx={{ color: '#65676b', mb: 1, display: 'block' }}>
{metadata.canonical_url || 'yourwebsite.com'}
</Typography>
{/* Title */}
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 1, lineHeight: 1.3 }}>
{metadata.open_graph?.title || metadata.seo_title || blogTitle}
</Typography>
{/* Description */}
<Typography variant="body2" sx={{ color: '#65676b', lineHeight: 1.4 }}>
{metadata.open_graph?.description || metadata.meta_description || 'Your description will appear here...'}
</Typography>
</Box>
</CardContent>
</Card>
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<TwitterIcon sx={{ color: '#1DA1F2' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Twitter Preview
</Typography>
<Chip label="Twitter Card" size="small" color="info" />
</Box>
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none', maxWidth: 400 }}>
<CardContent sx={{ p: 0 }}>
{/* Image placeholder */}
<Box sx={{
height: 200,
bgcolor: '#f5f5f5',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderBottom: '1px solid #e0e0e0'
}}>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{metadata.twitter_card?.image ? 'Image loaded' : 'No image set'}
</Typography>
</Box>
<Box sx={{ p: 2 }}>
{/* URL */}
<Typography variant="caption" sx={{ color: '#536471', mb: 1, display: 'block' }}>
{metadata.canonical_url || 'yourwebsite.com'}
</Typography>
{/* Title */}
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 1, lineHeight: 1.3 }}>
{metadata.twitter_card?.title || metadata.seo_title || blogTitle}
</Typography>
{/* Description */}
<Typography variant="body2" sx={{ color: '#536471', lineHeight: 1.4 }}>
{metadata.twitter_card?.description || metadata.meta_description || 'Your description will appear here...'}
</Typography>
{/* Twitter handle */}
{metadata.twitter_card?.site && (
<Typography variant="caption" sx={{ color: '#536471', mt: 1, display: 'block' }}>
{metadata.twitter_card.site}
</Typography>
)}
</Box>
</CardContent>
</Card>
</Paper>
</Grid>
{/* Rich Snippets Preview */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<CodeIcon sx={{ color: '#34A853' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Rich Snippets Preview
</Typography>
<Chip label="JSON-LD Schema" size="small" color="success" />
</Box>
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none' }}>
<CardContent sx={{ p: 2 }}>
{/* Article Schema Preview */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
{metadata.json_ld_schema?.headline || metadata.seo_title || blogTitle}
</Typography>
<Chip label="Article" size="small" color="success" />
</Box>
<Typography variant="body2" sx={{ color: '#4d5156', mb: 2 }}>
{metadata.json_ld_schema?.description || metadata.meta_description || 'Article description...'}
{/* Title */}
<Typography
variant="subtitle1"
sx={{
fontWeight: 600,
mb: 1,
lineHeight: 1.33,
fontSize: '15px',
color: '#0f1419',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
}}
>
{metadata.twitter_card?.title || metadata.seo_title || blogTitle}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}>
{metadata.json_ld_schema?.author?.name && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
By {metadata.json_ld_schema.author.name}
</Typography>
</Box>
)}
{metadata.json_ld_schema?.datePublished && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
{new Date(metadata.json_ld_schema.datePublished).toLocaleDateString()}
</Typography>
</Box>
)}
{metadata.reading_time && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
{metadata.reading_time} min read
</Typography>
</Box>
)}
{metadata.json_ld_schema?.wordCount && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#4d5156' }}>
{metadata.json_ld_schema.wordCount} words
</Typography>
</Box>
)}
</Box>
{/* Description */}
<Typography
variant="body2"
sx={{
color: '#536471',
lineHeight: 1.33,
fontSize: '15px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
}}
>
{metadata.twitter_card?.description || metadata.meta_description || 'Your description will appear here...'}
</Typography>
{metadata.json_ld_schema?.keywords && metadata.json_ld_schema.keywords.length > 0 && (
<Box sx={{ mt: 2 }}>
<Typography variant="caption" sx={{ color: '#4d5156', display: 'block', mb: 1 }}>
Keywords:
{/* Twitter handle */}
{metadata.twitter_card?.site && (
<Typography
variant="caption"
sx={{
color: '#536471',
mt: 1,
display: 'block',
fontSize: '13px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
}}
>
{metadata.twitter_card.site}
</Typography>
)}
</Box>
</CardContent>
</Card>
</Paper>
)}
{/* Rich Snippets Preview */}
{previewTabValue === 'richsnippets' && (
<Paper
sx={{
p: 3,
background: '#ffffff',
border: '1px solid #e0e0e0',
borderRadius: 2,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<CodeIcon sx={{ color: '#34A853', fontSize: 28 }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: '#202124' }}>
Rich Snippets Preview
</Typography>
<Chip label="JSON-LD Schema" size="small" sx={{ bgcolor: '#e8f5e9', color: '#34A853' }} />
</Box>
{/* Rich Snippets Card */}
<Card
sx={{
background: '#ffffff',
border: '1px solid #e0e0e0',
borderRadius: 2,
boxShadow: 'none',
maxWidth: 600
}}
>
<CardContent sx={{ p: 3 }}>
{/* Article Schema Preview */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#202124' }}>
{metadata.json_ld_schema?.headline || metadata.seo_title || blogTitle}
</Typography>
<Chip label="Article" size="small" sx={{ bgcolor: '#e8f5e9', color: '#34A853' }} />
</Box>
<Typography
variant="body1"
sx={{
color: '#4d5156',
mb: 2,
lineHeight: 1.6,
fontSize: '14px'
}}
>
{metadata.json_ld_schema?.description || metadata.meta_description || 'Article description...'}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap', mb: 2 }}>
{metadata.json_ld_schema?.author?.name && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#70757a', fontSize: '13px' }}>
By {metadata.json_ld_schema.author.name}
</Typography>
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{metadata.json_ld_schema.keywords.slice(0, 5).map((keyword: string, index: number) => (
<Chip key={index} label={keyword} size="small" variant="outlined" />
))}
</Box>
</Box>
)}
</CardContent>
</Card>
<Alert severity="success" sx={{ mt: 2 }}>
Rich snippets help search engines understand your content and may display additional information in search results
</Alert>
</Paper>
</Grid>
{/* Metadata Summary */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<SearchIcon />
Metadata Summary
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, bgcolor: 'rgba(76, 175, 80, 0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 600, color: 'success.main' }}>
{metadata.optimization_score || 0}%
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Optimization Score
{metadata.json_ld_schema?.datePublished && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#70757a', fontSize: '13px' }}>
{new Date(metadata.json_ld_schema.datePublished).toLocaleDateString()}
</Typography>
</Box>
)}
{metadata.reading_time && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#70757a', fontSize: '13px' }}>
{metadata.reading_time} min read
</Typography>
</Box>
)}
{metadata.json_ld_schema?.wordCount && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="caption" sx={{ color: '#70757a', fontSize: '13px' }}>
{metadata.json_ld_schema.wordCount} words
</Typography>
</Box>
)}
</Box>
{metadata.json_ld_schema?.keywords && metadata.json_ld_schema.keywords.length > 0 && (
<Box sx={{ mt: 2, pt: 2, borderTop: '1px solid #e0e0e0' }}>
<Typography variant="caption" sx={{ color: '#70757a', display: 'block', mb: 1, fontWeight: 500 }}>
Keywords:
</Typography>
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{metadata.json_ld_schema.keywords.slice(0, 5).map((keyword: string, index: number) => (
<Chip
key={index}
label={keyword}
size="small"
variant="outlined"
sx={{ borderColor: '#e0e0e0', color: '#4d5156' }}
/>
))}
</Box>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, bgcolor: 'rgba(33, 150, 243, 0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 600, color: 'primary.main' }}>
{metadata.reading_time || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Reading Time (min)
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, bgcolor: 'rgba(156, 39, 176, 0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 600, color: 'secondary.main' }}>
{metadata.blog_tags?.length || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Tags
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box sx={{ textAlign: 'center', p: 2, bgcolor: 'rgba(255, 152, 0, 0.1)', borderRadius: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 600, color: 'warning.main' }}>
{metadata.blog_categories?.length || 0}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Categories
</Typography>
</Box>
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
)}
</CardContent>
</Card>
</Paper>
)}
</Box>
);
};

View File

@@ -71,12 +71,25 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
return `${current}/${max}`;
};
// Consistent text input styling for better contrast
const textInputSx = {
'& .MuiInputBase-input': {
color: '#202124'
},
'& .MuiInputLabel-root': {
color: '#5f6368'
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#dadce0'
}
} as const;
const openGraph = metadata.open_graph || {};
const twitterCard = metadata.twitter_card || {};
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1, color: '#202124', fontWeight: 600 }}>
<ShareIcon sx={{ color: 'primary.main' }} />
Social Media Metadata
</Typography>
@@ -84,11 +97,11 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid container spacing={3}>
{/* Open Graph Section */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<FacebookIcon sx={{ color: '#1877F2' }} />
<LinkedInIcon sx={{ color: '#0077B5' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#202124' }}>
Open Graph Tags
</Typography>
<Chip label="Facebook & LinkedIn" size="small" color="primary" />
@@ -97,7 +110,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
OG Title
</Typography>
<Tooltip title="Copy to clipboard">
@@ -114,6 +127,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={openGraph.title || ''}
onChange={handleNestedFieldChange('open_graph', 'title')}
placeholder="Open Graph title (60 characters max)"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -131,7 +145,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
OG Description
</Typography>
<Tooltip title="Copy to clipboard">
@@ -150,6 +164,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={openGraph.description || ''}
onChange={handleNestedFieldChange('open_graph', 'description')}
placeholder="Open Graph description (160 characters max)"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -167,7 +182,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
OG Image URL
</Typography>
<Tooltip title="Copy to clipboard">
@@ -184,6 +199,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={openGraph.image || ''}
onChange={handleNestedFieldChange('open_graph', 'image')}
placeholder="https://example.com/image.jpg"
sx={textInputSx}
InputProps={{
startAdornment: (
<InputAdornment position="start">
@@ -196,7 +212,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
OG URL
</Typography>
<Tooltip title="Copy to clipboard">
@@ -213,6 +229,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={openGraph.url || ''}
onChange={handleNestedFieldChange('open_graph', 'url')}
placeholder="https://example.com/blog-post"
sx={textInputSx}
InputProps={{
startAdornment: (
<InputAdornment position="start">
@@ -224,18 +241,18 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
</Grid>
</Grid>
<Alert severity="info" sx={{ mt: 2 }}>
Open Graph tags are used by Facebook, LinkedIn, and other social platforms to display rich previews
</Alert>
<Typography variant="caption" sx={{ mt: 2, color: '#5f6368', display: 'block' }}>
Open Graph tags are used by Facebook, LinkedIn, and others to display rich previews.
</Typography>
</Paper>
</Grid>
{/* Twitter Card Section */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<TwitterIcon sx={{ color: '#1DA1F2' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#202124' }}>
Twitter Card Tags
</Typography>
<Chip label="Twitter & X" size="small" color="info" />
@@ -244,7 +261,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
Twitter Title
</Typography>
<Tooltip title="Copy to clipboard">
@@ -261,6 +278,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={twitterCard.title || ''}
onChange={handleNestedFieldChange('twitter_card', 'title')}
placeholder="Twitter card title (70 characters max)"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -278,7 +296,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
Twitter Description
</Typography>
<Tooltip title="Copy to clipboard">
@@ -297,6 +315,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={twitterCard.description || ''}
onChange={handleNestedFieldChange('twitter_card', 'description')}
placeholder="Twitter card description (200 characters max)"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -314,7 +333,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
Twitter Image URL
</Typography>
<Tooltip title="Copy to clipboard">
@@ -331,6 +350,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={twitterCard.image || ''}
onChange={handleNestedFieldChange('twitter_card', 'image')}
placeholder="https://example.com/twitter-image.jpg"
sx={textInputSx}
InputProps={{
startAdornment: (
<InputAdornment position="start">
@@ -343,7 +363,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
Twitter Site Handle
</Typography>
<Tooltip title="Copy to clipboard">
@@ -360,6 +380,7 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
value={twitterCard.site || ''}
onChange={handleNestedFieldChange('twitter_card', 'site')}
placeholder="@yourwebsite"
sx={textInputSx}
InputProps={{
startAdornment: (
<InputAdornment position="start">
@@ -371,16 +392,16 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
</Grid>
</Grid>
<Alert severity="info" sx={{ mt: 2 }}>
Twitter cards provide rich previews when your content is shared on Twitter/X
</Alert>
<Typography variant="caption" sx={{ mt: 2, color: '#5f6368', display: 'block' }}>
Twitter cards provide rich previews when your content is shared on Twitter/X.
</Typography>
</Paper>
</Grid>
{/* Social Media Preview */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1, color: '#202124' }}>
<ShareIcon />
Social Media Preview
</Typography>
@@ -388,22 +409,22 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
<Grid container spacing={2}>
{/* Facebook Preview */}
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #e0e0e0' }}>
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none', background: '#ffffff' }}>
<CardContent sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<FacebookIcon sx={{ color: '#1877F2' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
Facebook Preview
</Typography>
</Box>
<Box sx={{ border: '1px solid #e0e0e0', borderRadius: 1, p: 2, bgcolor: '#f5f5f5' }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
<Box sx={{ border: '1px solid #e0e0e0', borderRadius: 1, p: 2.5, bgcolor: '#fafafa' }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1, color: '#202124' }}>
{openGraph.title || 'Your Blog Title'}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary', mb: 1, display: 'block' }}>
<Typography variant="caption" sx={{ color: '#5f6368', mb: 1, display: 'block' }}>
{openGraph.url || 'yourwebsite.com'}
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.875rem' }}>
<Typography variant="body2" sx={{ fontSize: '0.875rem', color: '#5f6368' }}>
{openGraph.description || 'Your meta description will appear here...'}
</Typography>
</Box>
@@ -413,22 +434,22 @@ export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
{/* Twitter Preview */}
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #e0e0e0' }}>
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none', background: '#ffffff' }}>
<CardContent sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<TwitterIcon sx={{ color: '#1DA1F2' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: '#202124' }}>
Twitter Preview
</Typography>
</Box>
<Box sx={{ border: '1px solid #e0e0e0', borderRadius: 1, p: 2, bgcolor: '#f5f5f5' }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
<Box sx={{ border: '1px solid #e0e0e0', borderRadius: 1, p: 2.5, bgcolor: '#fafafa' }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1, color: '#202124' }}>
{twitterCard.title || 'Your Blog Title'}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary', mb: 1, display: 'block' }}>
<Typography variant="caption" sx={{ color: '#5f6368', mb: 1, display: 'block' }}>
{twitterCard.site || '@yourwebsite'}
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.875rem' }}>
<Typography variant="body2" sx={{ fontSize: '0.875rem', color: '#5f6368' }}>
{twitterCard.description || 'Your Twitter description will appear here...'}
</Typography>
</Box>

View File

@@ -56,6 +56,28 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
}) => {
const [showRawJson, setShowRawJson] = useState(false);
// Helpers for counters and consistent input styling
const getCharacterCountColor = (current: number, max: number) => {
if (current > max) return 'error';
if (current > max * 0.9) return 'warning';
return 'success';
};
const getCharacterCountText = (current: number, max: number) => {
if (current > max) return `${current}/${max} (Too long)`;
if (current > max * 0.9) return `${current}/${max} (Near limit)`;
return `${current}/${max}`;
};
const textInputSx = {
'& .MuiInputBase-input': {
color: '#202124'
},
'& .MuiInputLabel-root': {
color: '#5f6368'
}
} as const;
const handleTextFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
onMetadataEdit(field, event.target.value);
};
@@ -123,7 +145,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
<Grid container spacing={3}>
{/* Article Information */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon />
Article Schema
@@ -149,6 +171,19 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={jsonLdSchema.headline || ''}
onChange={handleSchemaFieldChange('headline')}
placeholder="Article headline"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((jsonLdSchema.headline || '').length, 110)}
>
{getCharacterCountText((jsonLdSchema.headline || '').length, 110)}
</Typography>
</InputAdornment>
)
}}
/>
</Grid>
@@ -173,6 +208,19 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={jsonLdSchema.description || ''}
onChange={handleSchemaFieldChange('description')}
placeholder="Article description"
sx={textInputSx}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((jsonLdSchema.description || '').length, 200)}
>
{getCharacterCountText((jsonLdSchema.description || '').length, 200)}
</Typography>
</InputAdornment>
)
}}
/>
</Grid>
@@ -202,6 +250,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
</InputAdornment>
)
}}
sx={textInputSx}
/>
</Grid>
@@ -228,6 +277,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
InputProps={{
endAdornment: <InputAdornment position="end">words</InputAdornment>
}}
sx={textInputSx}
/>
</Grid>
</Grid>
@@ -236,7 +286,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
{/* Author Information */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<PersonIcon />
Author Information
@@ -262,6 +312,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={author.name || ''}
onChange={handleAuthorFieldChange('name')}
placeholder="Author Name"
sx={textInputSx}
/>
</Grid>
@@ -284,6 +335,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={author['@type'] || ''}
onChange={handleAuthorFieldChange('@type')}
placeholder="Person"
sx={textInputSx}
/>
</Grid>
</Grid>
@@ -292,7 +344,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
{/* Publisher Information */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<BusinessIcon />
Publisher Information
@@ -318,6 +370,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={publisher.name || ''}
onChange={handlePublisherFieldChange('name')}
placeholder="Publisher Name"
sx={textInputSx}
/>
</Grid>
@@ -340,6 +393,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={publisher.logo || ''}
onChange={handlePublisherFieldChange('logo')}
placeholder="https://example.com/logo.png"
sx={textInputSx}
/>
</Grid>
</Grid>
@@ -348,7 +402,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
{/* Publication Dates */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CalendarIcon />
Publication Dates
@@ -375,6 +429,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={jsonLdSchema.datePublished || ''}
onChange={handleSchemaFieldChange('datePublished')}
InputLabelProps={{ shrink: true }}
sx={textInputSx}
/>
</Grid>
@@ -398,6 +453,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
value={jsonLdSchema.dateModified || ''}
onChange={handleSchemaFieldChange('dateModified')}
InputLabelProps={{ shrink: true }}
sx={textInputSx}
/>
</Grid>
</Grid>
@@ -406,7 +462,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
{/* Keywords */}
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={{ p: 3, background: '#ffffff', border: '1px solid #e0e0e0', borderRadius: 2, boxShadow: '0 2px 4px rgba(0,0,0,0.04)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon />
Keywords & Categories
@@ -438,6 +494,7 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
}}
placeholder="keyword1, keyword2, keyword3"
helperText="Separate keywords with commas"
sx={textInputSx}
/>
</Grid>
</Grid>
@@ -479,7 +536,9 @@ export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
readOnly: true,
sx: {
fontFamily: 'monospace',
fontSize: '0.875rem'
fontSize: '0.875rem',
background: '#0f172a',
color: '#e2e8f0'
}
}}
sx={{

View File

@@ -0,0 +1,264 @@
/**
* OverallScoreCard Component
*
* Renders the compact overall SEO score summary with grade chip and
* category score tiles.
*/
import React from 'react';
import {
Card,
CardHeader,
CardContent,
Typography,
Box,
Tooltip,
Paper,
Chip,
Avatar
} from '@mui/material';
interface MetricTooltip {
title: string;
description: string;
methodology: string;
score_meaning: string;
examples: string;
}
interface OverallScoreCardProps {
overallScore: number;
overallGrade: string;
statusLabel: string;
categoryScores: Record<string, number>;
getMetricTooltip: (category: string) => MetricTooltip;
getScoreColor: (score: number) => string;
}
const getGradeMeta = (grade: string) => {
switch (grade) {
case 'A':
return {
color: '#16a34a',
background: 'linear-gradient(135deg, rgba(34,197,94,0.12), rgba(22,163,74,0.18))',
tooltip: 'Grade A: Outstanding SEO health with only minor optimizations needed.'
};
case 'B':
return {
color: '#0ea5e9',
background: 'linear-gradient(135deg, rgba(14,165,233,0.12), rgba(2,132,199,0.18))',
tooltip: 'Grade B: Strong SEO foundation with several opportunities to optimize further.'
};
case 'C':
return {
color: '#d97706',
background: 'linear-gradient(135deg, rgba(251,191,36,0.14), rgba(217,119,6,0.2))',
tooltip: 'Grade C: Moderate SEO performance. Prioritize improvements in weaker categories.'
};
case 'D':
return {
color: '#ea580c',
background: 'linear-gradient(135deg, rgba(251,113,133,0.14), rgba(249,115,22,0.2))',
tooltip: 'Grade D: Significant SEO gaps detected. Address critical issues promptly.'
};
default:
return {
color: '#475569',
background: 'linear-gradient(135deg, rgba(148,163,184,0.14), rgba(100,116,139,0.2))',
tooltip: 'SEO grade unavailable. Review analysis details for more information.'
};
}
};
export const OverallScoreCard: React.FC<OverallScoreCardProps> = ({
overallScore,
overallGrade,
statusLabel,
categoryScores,
getMetricTooltip,
getScoreColor
}) => {
const gradeMeta = getGradeMeta(overallGrade);
return (
<Card
sx={{
mb: 3,
background: 'rgba(255,255,255,0.95)',
border: '1px solid rgba(0,0,0,0.08)',
boxShadow: '0 8px 24px rgba(15,23,42,0.04)',
borderRadius: 3
}}
>
<CardHeader
sx={{
pb: 0,
'& .MuiCardHeader-content': {
overflow: 'hidden'
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography
variant="subtitle1"
sx={{ fontWeight: 700, letterSpacing: 0.2, color: '#0f172a' }}
>
Overall SEO Performance Snapshot
</Typography>
</Box>
</CardHeader>
<CardContent
sx={{
pt: 2,
pb: { xs: 2.5, md: 3 },
px: { xs: 2, md: 3 }
}}
>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
gap: { xs: 3, md: 4 },
alignItems: { xs: 'stretch', md: 'flex-start' }
}}
>
<Box
sx={{
flexShrink: 0,
minWidth: { md: 240 },
display: 'flex',
flexDirection: 'column',
alignItems: { xs: 'flex-start', md: 'center' },
gap: 1.5,
background: 'linear-gradient(145deg, rgba(241,245,249,0.7), rgba(255,255,255,0.95))',
borderRadius: 2,
p: { xs: 1.5, md: 2 }
}}
>
<Box sx={{ textAlign: { xs: 'left', md: 'center' } }}>
<Typography
component="span"
sx={{
display: 'inline-flex',
alignItems: 'baseline',
gap: 1,
fontWeight: 800,
fontSize: { xs: '2.4rem', md: '2.8rem' },
lineHeight: 1,
background: 'linear-gradient(120deg, #22c55e, #4ade80)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
}}
>
{overallScore}
<Typography
component="span"
variant="caption"
sx={{ color: '#64748b', fontWeight: 600 }}
>
/100
</Typography>
</Typography>
<Typography variant="caption" sx={{ color: '#64748b', display: 'block', mt: 0.5 }}>
Overall Score
</Typography>
</Box>
<Tooltip title={gradeMeta.tooltip} arrow placement="top">
<Chip
label={statusLabel}
avatar={
<Avatar
sx={{
bgcolor: '#fff',
color: gradeMeta.color,
fontWeight: 700
}}
>
{overallGrade}
</Avatar>
}
sx={{
fontWeight: 600,
px: 2.2,
py: 0.5,
letterSpacing: 0.3,
color: gradeMeta.color,
background: gradeMeta.background
}}
/>
</Tooltip>
</Box>
<Box sx={{ flex: 1 }}>
<Box
sx={{
display: 'grid',
gridTemplateColumns: { xs: 'repeat(2, minmax(110px, 1fr))', sm: 'repeat(3, minmax(110px, 1fr))' },
gap: 1.5
}}
>
{Object.entries(categoryScores).map(([category, score]) => {
const tooltip = getMetricTooltip(category);
return (
<Tooltip
key={category}
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
{tooltip.title}
</Typography>
<Typography variant="body2" sx={{ mb: 0.75, color: '#475569' }}>
{tooltip.description}
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, color: '#64748b' }}>
<strong>Methodology:</strong> {tooltip.methodology}
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, color: '#64748b' }}>
<strong>Score Meaning:</strong> {tooltip.score_meaning}
</Typography>
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Examples:</strong> {tooltip.examples}
</Typography>
</Box>
}
arrow
placement="top"
>
<Paper
sx={{
p: 1.4,
textAlign: 'center',
borderRadius: 2,
backgroundColor: '#ffffff',
border: '1px solid #e2e8f0',
boxShadow: '0 8px 18px rgba(15,23,42,0.06)',
cursor: 'help'
}}
>
<Typography
variant="h6"
sx={{ fontWeight: 800, color: getScoreColor(score), mb: 0.35 }}
>
{score}
</Typography>
<Typography
variant="caption"
sx={{ color: '#64748b', textTransform: 'capitalize', fontWeight: 600 }}
>
{category.replace('_', ' ')}
</Typography>
</Paper>
</Tooltip>
);
})}
</Box>
</Box>
</Box>
</CardContent>
</Card>
);
};
export default OverallScoreCard;

View File

@@ -1,6 +1,6 @@
/**
* Readability Analysis Component
*
*
* Displays comprehensive readability analysis including readability metrics,
* content statistics, sentence/paragraph analysis, and target audience information.
*/
@@ -14,7 +14,7 @@ import {
IconButton,
Tooltip
} from '@mui/material';
import {
import {
MenuBook
} from '@mui/icons-material';
@@ -57,109 +57,186 @@ interface ReadabilityAnalysisProps {
};
}
export const ReadabilityAnalysis: React.FC<ReadabilityAnalysisProps> = ({
detailedAnalysis,
visualizationData
const cardStyles = {
p: 3,
backgroundColor: '#ffffff',
border: '1px solid #e2e8f0',
borderRadius: 2,
boxShadow: '0 12px 30px rgba(15,23,42,0.08)',
color: '#0f172a',
minHeight: '100%'
} as const;
const sectionTitleSx = {
fontWeight: 700,
letterSpacing: 0.2,
color: '#0f172a',
mb: 2
} as const;
const statRowSx = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
py: 0.5
} as const;
const statLabelSx = {
color: '#475569',
fontWeight: 500
} as const;
const statValueSx = {
color: '#0f172a',
fontWeight: 700
} as const;
const metricRowSx = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.65rem 0.85rem',
borderRadius: 12,
backgroundColor: '#f1f5f9',
cursor: 'help',
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 10px 20px rgba(15,23,42,0.08)'
}
} as const;
export const ReadabilityAnalysis: React.FC<ReadabilityAnalysisProps> = ({
detailedAnalysis,
visualizationData
}) => {
const readabilityMetrics = detailedAnalysis?.readability_analysis?.metrics ?? {};
const getMetricDetails = (metric: string, value: number) => {
const tooltips: Record<string, { description: string; interpretation: string }> = {
flesch_reading_ease: {
description: 'Measures how easy text is to read (0-100 scale).',
interpretation: value >= 80 ? 'Very Easy' : value >= 60 ? 'Standard' : 'Challenging'
},
flesch_kincaid_grade: {
description: 'U.S. grade level required to understand the text.',
interpretation: value <= 8 ? 'Easy' : value <= 12 ? 'Moderate' : 'Advanced'
},
gunning_fog: {
description: 'Years of formal education needed for comprehension.',
interpretation: value <= 12 ? 'Easy' : value <= 16 ? 'Moderate' : 'Advanced'
},
smog_index: {
description: 'Estimates the years of education needed to understand the text.',
interpretation: value <= 8 ? 'Easy' : value <= 12 ? 'Moderate' : 'Advanced'
},
automated_readability: {
description: 'Automated readability score based on characters per word.',
interpretation: value <= 8 ? 'Easy' : value <= 12 ? 'Moderate' : 'Advanced'
},
coleman_liau: {
description: 'Readability based on characters per word and sentence length.',
interpretation: value <= 8 ? 'Easy' : value <= 12 ? 'Moderate' : 'Advanced'
}
};
return (
tooltips[metric] || {
description: 'Readability metric',
interpretation: 'No interpretation available'
}
);
};
const renderStatRow = (label: React.ReactNode, value: React.ReactNode) => (
<Box sx={statRowSx}>
<Typography variant="body2" sx={statLabelSx}>
{label}
</Typography>
<Typography variant="body2" sx={statValueSx}>
{value}
</Typography>
</Box>
);
return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<MenuBook sx={{ color: 'primary.main' }} />
<Typography variant="h6" component="h3" sx={{ fontWeight: 600 }}>
<Typography
variant="h6"
component="h3"
sx={{ fontWeight: 700, letterSpacing: 0.3, color: '#0f172a' }}
>
Readability Analysis
</Typography>
</Box>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Paper sx={cardStyles}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
<Typography variant="subtitle1" sx={sectionTitleSx}>
Readability Metrics
</Typography>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
Readability Analysis
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
Measures how easy your content is to read and understand.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<Typography variant="caption" sx={{ display: 'block', mb: 0.75, color: '#64748b' }}>
<strong>Flesch Reading Ease:</strong> 90-100 (Very Easy), 80-89 (Easy), 70-79 (Fairly Easy), 60-69 (Standard)
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Average Sentence Length:</strong> 15-20 words is optimal
<Typography variant="caption" sx={{ display: 'block', mb: 0.75, color: '#64748b' }}>
<strong>Sentence Length:</strong> 15-20 words is optimal
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Average Syllables per Word:</strong> 1.5-1.7 is ideal
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Syllables per Word:</strong> 1.5-1.7 keeps content approachable
</Typography>
</Box>
}
arrow
>
<IconButton size="small" sx={{ color: 'primary.main' }}>
<MenuBook />
<MenuBook fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{detailedAnalysis?.readability_analysis?.metrics && Object.keys(detailedAnalysis.readability_analysis.metrics).length > 0 ? (
Object.entries(detailedAnalysis.readability_analysis.metrics).map(([metric, value]) => {
const getReadabilityTooltip = (metric: string, value: number) => {
const tooltips = {
flesch_reading_ease: {
description: "Measures how easy text is to read (0-100 scale)",
interpretation: value >= 80 ? "Very Easy" : value >= 60 ? "Standard" : "Difficult"
},
flesch_kincaid_grade: {
description: "U.S. grade level needed to understand the text",
interpretation: value <= 8 ? "Easy" : value <= 12 ? "Moderate" : "Difficult"
},
gunning_fog: {
description: "Years of formal education needed to understand the text",
interpretation: value <= 12 ? "Easy" : value <= 16 ? "Moderate" : "Difficult"
},
smog_index: {
description: "Simple Measure of Gobbledygook - readability formula",
interpretation: value <= 8 ? "Easy" : value <= 12 ? "Moderate" : "Difficult"
},
automated_readability: {
description: "Automated Readability Index based on character count",
interpretation: value <= 8 ? "Easy" : value <= 12 ? "Moderate" : "Difficult"
},
coleman_liau: {
description: "Readability test based on average sentence length and characters per word",
interpretation: value <= 8 ? "Easy" : value <= 12 ? "Moderate" : "Difficult"
}
};
return tooltips[metric as keyof typeof tooltips] || { description: "Readability metric", interpretation: "N/A" };
};
const tooltip = getReadabilityTooltip(metric, value);
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.25 }}>
{Object.keys(readabilityMetrics).length > 0 ? (
Object.entries(readabilityMetrics).map(([metric, value]) => {
const { description, interpretation } = getMetricDetails(metric, value);
const label = metric.replace('_', ' ').replace(/\b\w/g, (l) => l.toUpperCase());
return (
<Tooltip
key={metric}
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
{metric.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
{label}
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
{tooltip.description}
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
{description}
</Typography>
<Typography variant="caption">
<strong>Interpretation:</strong> {tooltip.interpretation}
<Typography variant="caption" sx={{ color: '#64748b' }}>
<strong>Interpretation:</strong> {interpretation}
</Typography>
</Box>
}
arrow
placement="top"
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', p: 1, borderRadius: 1, background: 'rgba(0,0,0,0.02)', cursor: 'help' }}>
<Typography variant="body2" sx={{ textTransform: 'capitalize' }}>
<Box sx={metricRowSx}>
<Typography variant="body2" sx={{ textTransform: 'capitalize', color: '#334155' }}>
{metric.replace('_', ' ')}
</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
<Typography variant="body2" sx={{ fontWeight: 700, color: '#0f172a' }}>
{value.toFixed(1)}
</Typography>
</Box>
@@ -167,116 +244,72 @@ export const ReadabilityAnalysis: React.FC<ReadabilityAnalysisProps> = ({
);
})
) : (
<Typography variant="body2" sx={{ color: 'text.secondary', fontStyle: 'italic' }}>
<Typography variant="body2" sx={{ color: '#64748b', fontStyle: 'italic' }}>
No readability metrics available. This may indicate an issue with the content analysis.
</Typography>
)}
</Box>
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={cardStyles}>
<Typography variant="subtitle1" sx={sectionTitleSx}>
Content Statistics
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Word Count</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_quality?.word_count || visualizationData?.content_stats.word_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Sections</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.total_sections || visualizationData?.content_stats.sections}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Paragraphs</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.total_paragraphs || visualizationData?.content_stats.paragraphs}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Sentences</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.total_sentences || 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Unique Words</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_quality?.unique_words || 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Vocabulary Diversity</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_quality?.vocabulary_diversity ?
(detailedAnalysis.content_quality.vocabulary_diversity * 100).toFixed(1) + '%' : 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}>
{renderStatRow('Word Count', detailedAnalysis?.content_quality?.word_count || visualizationData?.content_stats.word_count || 'N/A')}
{renderStatRow('Sections', detailedAnalysis?.content_structure?.total_sections || visualizationData?.content_stats.sections || 'N/A')}
{renderStatRow('Paragraphs', detailedAnalysis?.content_structure?.total_paragraphs || visualizationData?.content_stats.paragraphs || 'N/A')}
{renderStatRow('Sentences', detailedAnalysis?.content_structure?.total_sentences || 'N/A')}
{renderStatRow('Unique Words', detailedAnalysis?.content_quality?.unique_words || 'N/A')}
{renderStatRow(
'Vocabulary Diversity',
detailedAnalysis?.content_quality?.vocabulary_diversity !== undefined
? `${(detailedAnalysis.content_quality.vocabulary_diversity * 100).toFixed(1)}%`
: 'N/A'
)}
</Box>
</Paper>
</Grid>
</Grid>
{/* Additional Readability Metrics */}
<Grid container spacing={3} sx={{ mt: 2 }}>
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={cardStyles}>
<Typography variant="subtitle1" sx={sectionTitleSx}>
Sentence & Paragraph Analysis
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Avg Sentence Length</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.readability_analysis?.avg_sentence_length?.toFixed(1) || 'N/A'} words
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Avg Paragraph Length</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.readability_analysis?.avg_paragraph_length?.toFixed(1) || 'N/A'} words
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Transition Words</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_quality?.transition_words_used || 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}>
{renderStatRow(
'Average Sentence Length',
detailedAnalysis?.readability_analysis?.avg_sentence_length !== undefined
? `${detailedAnalysis.readability_analysis.avg_sentence_length.toFixed(1)} words`
: 'N/A'
)}
{renderStatRow(
'Average Paragraph Length',
detailedAnalysis?.readability_analysis?.avg_paragraph_length !== undefined
? `${detailedAnalysis.readability_analysis.avg_paragraph_length.toFixed(1)} words`
: 'N/A'
)}
{renderStatRow(
'Transition Words Used',
detailedAnalysis?.content_quality?.transition_words_used || 'N/A'
)}
</Box>
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={cardStyles}>
<Typography variant="subtitle1" sx={sectionTitleSx}>
Target Audience
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Reading Level</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.readability_analysis?.target_audience || 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Content Depth Score</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_quality?.content_depth_score || 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2">Flow Score</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_quality?.flow_score || 'N/A'}
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}>
{renderStatRow('Reading Level', detailedAnalysis?.readability_analysis?.target_audience || 'N/A')}
{renderStatRow('Content Depth Score', detailedAnalysis?.content_quality?.content_depth_score || 'N/A')}
{renderStatRow('Flow Score', detailedAnalysis?.content_quality?.flow_score || 'N/A')}
</Box>
</Paper>
</Grid>

View File

@@ -1,6 +1,6 @@
/**
* Recommendations Component
*
*
* Displays actionable SEO recommendations with priority indicators,
* category tags, and impact descriptions.
*/
@@ -12,7 +12,7 @@ import {
Paper,
Chip
} from '@mui/material';
import {
import {
Lightbulb,
CheckCircle,
Cancel,
@@ -30,78 +30,107 @@ interface RecommendationsProps {
recommendations: Recommendation[];
}
const priorityStyles: Record<string, { color: string; gradient: string }> = {
High: { color: '#dc2626', gradient: 'linear-gradient(135deg, rgba(248,113,113,0.12), rgba(239,68,68,0.18))' },
Medium: { color: '#d97706', gradient: 'linear-gradient(135deg, rgba(251,191,36,0.12), rgba(217,119,6,0.16))' },
Low: { color: '#16a34a', gradient: 'linear-gradient(135deg, rgba(74,222,128,0.12), rgba(22,163,74,0.16))' },
default: { color: '#475569', gradient: 'linear-gradient(135deg, rgba(148,163,184,0.1), rgba(100,116,139,0.14))' }
};
export const Recommendations: React.FC<RecommendationsProps> = ({ recommendations }) => {
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'High': return 'error.main';
case 'Medium': return 'warning.main';
case 'Low': return 'success.main';
default: return 'text.secondary';
}
};
const getPriorityColor = (priority: string) => priorityStyles[priority]?.color || priorityStyles.default.color;
const getPriorityIcon = (priority: string) => {
switch (priority) {
case 'High': return <Cancel sx={{ fontSize: 16 }} />;
case 'Medium': return <Warning sx={{ fontSize: 16 }} />;
case 'Low': return <CheckCircle sx={{ fontSize: 16 }} />;
default: return <Warning sx={{ fontSize: 16 }} />;
case 'High':
return <Cancel sx={{ fontSize: 18 }} />;
case 'Medium':
return <Warning sx={{ fontSize: 18 }} />;
case 'Low':
return <CheckCircle sx={{ fontSize: 18 }} />;
default:
return <Warning sx={{ fontSize: 18 }} />;
}
};
const getScoreBadgeVariant = (score: number) => {
if (score >= 80) return 'success';
if (score >= 60) return 'warning';
return 'error';
const getChipColor = (priority: string) => {
switch (priority) {
case 'High':
return 'error';
case 'Medium':
return 'warning';
case 'Low':
return 'success';
default:
return 'default';
}
};
return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<Lightbulb sx={{ color: 'primary.main' }} />
<Typography variant="h6" component="h3" sx={{ fontWeight: 600 }}>
<Typography variant="h6" component="h3" sx={{ fontWeight: 700, letterSpacing: 0.2, color: '#0f172a' }}>
Actionable Recommendations
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{recommendations.map((rec, index) => (
<Paper
key={index}
sx={{
p: 3,
border: '1px solid rgba(255,255,255,0.1)',
background: 'rgba(255,255,255,0.03)',
borderRadius: 2
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Box sx={{ color: getPriorityColor(rec.priority), mt: 0.5 }}>
{getPriorityIcon(rec.priority)}
</Box>
<Box sx={{ flex: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Chip
label={rec.category}
variant="outlined"
size="small"
sx={{ borderColor: 'rgba(255,255,255,0.3)' }}
/>
<Chip
label={rec.priority}
color={getScoreBadgeVariant(rec.priority === 'High' ? 30 : 70)}
size="small"
/>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
{recommendations.map((rec, index) => {
const styles = priorityStyles[rec.priority] || priorityStyles.default;
return (
<Paper
key={index}
sx={{
p: 3,
background: '#ffffff',
border: '1px solid #e2e8f0',
borderRadius: 3,
boxShadow: '0 16px 36px rgba(15,23,42,0.08)',
color: '#0f172a'
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Box
sx={{
mt: 0.5,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 32,
height: 32,
borderRadius: '999px',
background: styles.gradient,
color: getPriorityColor(rec.priority)
}}
>
{getPriorityIcon(rec.priority)}
</Box>
<Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 1 }}>
<Chip
label={rec.category}
variant="outlined"
size="small"
sx={{ borderColor: '#cbd5f5', color: '#475569', fontWeight: 600 }}
/>
<Chip
label={rec.priority}
color={getChipColor(rec.priority)}
size="small"
sx={{ fontWeight: 600 }}
/>
</Box>
<Typography variant="body1" sx={{ lineHeight: 1.6, color: '#1f2937' }}>
{rec.recommendation}
</Typography>
<Typography variant="caption" sx={{ color: '#64748b' }}>
{rec.impact}
</Typography>
</Box>
<Typography variant="body2" sx={{ mb: 1 }}>
{rec.recommendation}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{rec.impact}
</Typography>
</Box>
</Box>
</Paper>
))}
</Paper>
);
})}
</Box>
</Box>
);

View File

@@ -1,6 +1,6 @@
/**
* Structure Analysis Component
*
*
* Displays comprehensive content structure analysis including structure overview,
* content elements detection, and heading structure analysis.
*/
@@ -14,7 +14,7 @@ import {
Chip,
Tooltip
} from '@mui/material';
import {
import {
BarChart
} from '@mui/icons-material';
@@ -52,127 +52,157 @@ interface StructureAnalysisProps {
};
}
const baseCard = {
p: 3,
backgroundColor: '#ffffff',
border: '1px solid #e2e8f0',
borderRadius: 2,
boxShadow: '0 12px 28px rgba(15,23,42,0.08)',
color: '#0f172a',
minHeight: '100%'
} as const;
const infoRow = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.75rem 0',
cursor: 'help'
} as const;
const statLabel = {
color: '#475569',
fontWeight: 500
} as const;
const statValue = {
color: '#0f172a',
fontWeight: 700
} as const;
const highlightCard = (borderColor: string) => ({
p: 2,
borderRadius: 2,
border: `1px solid ${borderColor}`,
background: `linear-gradient(140deg, ${borderColor}15, ${borderColor}22)`
});
export const StructureAnalysis: React.FC<StructureAnalysisProps> = ({ detailedAnalysis }) => {
const structure = detailedAnalysis?.content_structure;
const quality = detailedAnalysis?.content_quality;
const headings = detailedAnalysis?.heading_structure;
return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<BarChart sx={{ color: 'primary.main' }} />
<Typography variant="h6" component="h3" sx={{ fontWeight: 600 }}>
<Typography variant="h6" component="h3" sx={{ fontWeight: 700, letterSpacing: 0.2, color: '#0f172a' }}>
Content Structure Analysis
</Typography>
</Box>
<Grid container spacing={3}>
{/* Content Structure Overview */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={baseCard}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 2, color: '#0f172a' }}>
Structure Overview
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
Total Sections
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
Number of main content sections (H2 headings) in your blog post.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, color: '#64748b' }}>
<strong>Optimal Range:</strong> 3-8 sections for most blog posts
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Good sectioning improves readability and helps search engines understand your content structure.
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Why it matters:</strong> Good sectioning improves readability and structure.
</Typography>
</Box>
}
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Total Sections</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.total_sections || 'N/A'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Total Sections</Typography>
<Typography variant="body2" sx={statValue}>
{structure?.total_sections || 'N/A'}
</Typography>
</Box>
</Tooltip>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
Total Paragraphs
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
Number of paragraphs in your content (excluding headings).
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Optimal Range:</strong> 8-20 paragraphs for most blog posts
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Appropriate paragraph count indicates good content depth and organization.
</Typography>
</Box>
}
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Total Paragraphs</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.total_paragraphs || 'N/A'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Total Paragraphs</Typography>
<Typography variant="body2" sx={statValue}>
{structure?.total_paragraphs || 'N/A'}
</Typography>
</Box>
</Tooltip>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
Total Sentences
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
Total number of sentences in your content.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Optimal Range:</strong> 40-100 sentences for most blog posts
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Sentence count affects readability and content comprehensiveness.
</Typography>
</Box>
}
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Total Sentences</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.total_sentences || 'N/A'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Total Sentences</Typography>
<Typography variant="body2" sx={statValue}>
{structure?.total_sentences || 'N/A'}
</Typography>
</Box>
</Tooltip>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, color: '#0f172a' }}>
Structure Score
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ mb: 1, color: '#475569' }}>
Overall score (0-100) for your content's structural organization.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Scoring Factors:</strong> Section count, paragraph count, introduction/conclusion presence
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Well-structured content ranks better and provides better user experience.
<Typography variant="caption" sx={{ display: 'block', color: '#64748b' }}>
<strong>Scoring Factors:</strong> Section count, paragraph count, intro/conclusion presence.
</Typography>
</Box>
}
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Structure Score</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{detailedAnalysis?.content_structure?.structure_score || 'N/A'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Structure Score</Typography>
<Typography variant="body2" sx={statValue}>
{structure?.structure_score || 'N/A'}
</Typography>
</Box>
</Tooltip>
@@ -182,94 +212,52 @@ export const StructureAnalysis: React.FC<StructureAnalysisProps> = ({ detailedAn
{/* Content Elements */}
<Grid item xs={12} md={6}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={baseCard}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 2, color: '#0f172a' }}>
Content Elements
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Introduction Section
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Whether your content has a clear introduction that sets context and expectations.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Why it matters:</strong> Introductions help readers understand what they'll learn and improve engagement.
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>SEO Impact:</strong> Clear introductions help search engines understand your content's purpose.
</Typography>
</Box>
}
title="Whether your content has a clear introduction that sets context and expectations."
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Has Introduction</Typography>
<Chip
label={detailedAnalysis?.content_structure?.has_introduction ? 'Yes' : 'No'}
color={detailedAnalysis?.content_structure?.has_introduction ? 'success' : 'error'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Has Introduction</Typography>
<Chip
label={structure?.has_introduction ? 'Yes' : 'No'}
color={structure?.has_introduction ? 'success' : 'error'}
size="small"
sx={{ fontWeight: 600 }}
/>
</Box>
</Tooltip>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Conclusion Section
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Whether your content has a clear conclusion that summarizes key points.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Why it matters:</strong> Conclusions help readers retain information and provide closure.
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>SEO Impact:</strong> Good conclusions can improve time on page and reduce bounce rate.
</Typography>
</Box>
}
title="Whether your content ends with a clear conclusion summarizing key points."
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Has Conclusion</Typography>
<Chip
label={detailedAnalysis?.content_structure?.has_conclusion ? 'Yes' : 'No'}
color={detailedAnalysis?.content_structure?.has_conclusion ? 'success' : 'error'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Has Conclusion</Typography>
<Chip
label={structure?.has_conclusion ? 'Yes' : 'No'}
color={structure?.has_conclusion ? 'success' : 'error'}
size="small"
sx={{ fontWeight: 600 }}
/>
</Box>
</Tooltip>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Call to Action
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Whether your content includes a clear call to action for readers.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Why it matters:</strong> CTAs guide readers to take desired actions and improve conversion rates.
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>SEO Impact:</strong> Strong CTAs can improve user engagement metrics.
</Typography>
</Box>
}
title="Whether your content includes a clear call to action for readers."
arrow
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'help' }}>
<Typography variant="body2">Has Call to Action</Typography>
<Chip
label={detailedAnalysis?.content_structure?.has_call_to_action ? 'Yes' : 'No'}
color={detailedAnalysis?.content_structure?.has_call_to_action ? 'success' : 'error'}
<Box sx={infoRow}>
<Typography variant="body2" sx={statLabel}>Has Call to Action</Typography>
<Chip
label={structure?.has_call_to_action ? 'Yes' : 'No'}
color={structure?.has_call_to_action ? 'success' : 'error'}
size="small"
sx={{ fontWeight: 600 }}
/>
</Box>
</Tooltip>
@@ -281,193 +269,104 @@ export const StructureAnalysis: React.FC<StructureAnalysisProps> = ({ detailedAn
{/* Content Quality Metrics */}
<Grid container spacing={3} sx={{ mt: 2 }}>
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
<Paper sx={baseCard}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 2, color: '#0f172a' }}>
Content Quality Metrics
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Word Count
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Total number of words in your content.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Optimal Range:</strong> 800-2000 words for most blog posts
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Longer content typically ranks better and provides more value to readers.
</Typography>
</Box>
}
title="Total number of words in your content. Longer content typically ranks better."
arrow
>
<Box sx={{ p: 2, background: 'rgba(76, 175, 80, 0.1)', borderRadius: 2, border: '1px solid rgba(76, 175, 80, 0.3)', cursor: 'help' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'success.main', mb: 1 }}>
<Box sx={highlightCard('rgba(34,197,94,0.65)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#15803d', mb: 1 }}>
Word Count
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
{detailedAnalysis?.content_quality?.word_count || 'N/A'}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{quality?.word_count || 'N/A'}
</Typography>
</Box>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Vocabulary Diversity
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Ratio of unique words to total words, indicating content variety.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Optimal Range:</strong> 0.4-0.7 (40-70% unique words)
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Higher diversity indicates richer, more engaging content.
</Typography>
</Box>
}
title="Ratio of unique words to total words, indicating content variety and richness."
arrow
>
<Box sx={{ p: 2, background: 'rgba(33, 150, 243, 0.1)', borderRadius: 2, border: '1px solid rgba(33, 150, 243, 0.3)', cursor: 'help' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'primary.main', mb: 1 }}>
<Box sx={highlightCard('rgba(59,130,246,0.65)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#1d4ed8', mb: 1 }}>
Vocabulary Diversity
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
{detailedAnalysis?.content_quality?.vocabulary_diversity ?
(detailedAnalysis.content_quality.vocabulary_diversity * 100).toFixed(1) + '%' : 'N/A'}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{quality?.vocabulary_diversity !== undefined
? `${(quality.vocabulary_diversity * 100).toFixed(1)}%`
: 'N/A'}
</Typography>
</Box>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Content Depth Score
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Score (0-100) indicating how comprehensive and detailed your content is.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Scoring Factors:</strong> Word count, section depth, information density
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Deeper content provides more value and ranks better in search results.
</Typography>
</Box>
}
title="Score (0-100) indicating how comprehensive and detailed your content is."
arrow
>
<Box sx={{ p: 2, background: 'rgba(156, 39, 176, 0.1)', borderRadius: 2, border: '1px solid rgba(156, 39, 176, 0.3)', cursor: 'help' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'secondary.main', mb: 1 }}>
<Box sx={highlightCard('rgba(168,85,247,0.65)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#7c3aed', mb: 1 }}>
Content Depth Score
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
{detailedAnalysis?.content_quality?.content_depth_score || 'N/A'}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{quality?.content_depth_score || 'N/A'}
</Typography>
</Box>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Flow Score
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Score (0-100) indicating how well your content flows from one idea to the next.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Scoring Factors:</strong> Transition words, sentence variety, logical progression
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Good flow improves readability and keeps readers engaged.
</Typography>
</Box>
}
title="Score (0-100) indicating how well your content flows from one idea to the next."
arrow
>
<Box sx={{ p: 2, background: 'rgba(255, 152, 0, 0.1)', borderRadius: 2, border: '1px solid rgba(255, 152, 0, 0.3)', cursor: 'help' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'warning.main', mb: 1 }}>
<Box sx={highlightCard('rgba(14,165,233,0.6)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#0284c7', mb: 1 }}>
Flow Score
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
{detailedAnalysis?.content_quality?.flow_score || 'N/A'}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{quality?.flow_score || 'N/A'}
</Typography>
</Box>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Transition Words
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Number of transition words used to connect ideas and improve flow.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Optimal Range:</strong> 5-15 transition words for most blog posts
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Transition words improve readability and help readers follow your logic.
</Typography>
</Box>
}
title="Number of transition words used higher values suggest smoother narrative flow."
arrow
>
<Box sx={{ p: 2, background: 'rgba(244, 67, 54, 0.1)', borderRadius: 2, border: '1px solid rgba(244, 67, 54, 0.3)', cursor: 'help' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'error.main', mb: 1 }}>
Transition Words
<Box sx={highlightCard('rgba(251,191,36,0.6)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#b45309', mb: 1 }}>
Transition Words Used
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
{detailedAnalysis?.content_quality?.transition_words_used || 'N/A'}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{quality?.transition_words_used || 'N/A'}
</Typography>
</Box>
</Tooltip>
</Grid>
<Grid item xs={12} md={4}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Unique Words
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Number of unique words used in your content.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Why it matters:</strong> More unique words indicate richer vocabulary and better content variety.
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>SEO Impact:</strong> Diverse vocabulary can help with semantic SEO and topic coverage.
</Typography>
</Box>
}
title="Average unique words used throughout the article. Indicates lexical richness."
arrow
>
<Box sx={{ p: 2, background: 'rgba(0, 150, 136, 0.1)', borderRadius: 2, border: '1px solid rgba(0, 150, 136, 0.3)', cursor: 'help' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'info.main', mb: 1 }}>
<Box sx={highlightCard('rgba(244,114,182,0.6)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#be185d', mb: 1 }}>
Unique Words
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
{detailedAnalysis?.content_quality?.unique_words || 'N/A'}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{quality?.unique_words || 'N/A'}
</Typography>
</Box>
</Tooltip>
@@ -478,136 +377,58 @@ export const StructureAnalysis: React.FC<StructureAnalysisProps> = ({ detailedAn
</Grid>
{/* Heading Structure */}
<Grid container spacing={3} sx={{ mt: 2 }}>
<Grid item xs={12}>
<Paper sx={{ p: 3, background: 'rgba(255,255,255,0.8)', border: '1px solid rgba(0,0,0,0.1)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
Heading Structure Analysis
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, background: 'rgba(76, 175, 80, 0.1)', borderRadius: 2, border: '1px solid rgba(76, 175, 80, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'success.main', mb: 1 }}>
H1 Headings ({detailedAnalysis?.heading_structure?.h1_count || 0})
</Typography>
{detailedAnalysis?.heading_structure?.h1_headings?.map((heading: string, index: number) => (
<Typography key={index} variant="caption" sx={{ display: 'block', mb: 0.5 }}>
• {heading}
{headings && (
<Grid container spacing={3} sx={{ mt: 2 }}>
<Grid item xs={12}>
<Paper sx={baseCard}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 2, color: '#0f172a' }}>
Heading Structure
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Box sx={highlightCard('rgba(59,130,246,0.45)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#1d4ed8', mb: 1 }}>
H1 Headings
</Typography>
))}
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, background: 'rgba(33, 150, 243, 0.1)', borderRadius: 2, border: '1px solid rgba(33, 150, 243, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'primary.main', mb: 1 }}>
H2 Headings ({detailedAnalysis?.heading_structure?.h2_count || 0})
</Typography>
{detailedAnalysis?.heading_structure?.h2_headings?.slice(0, 3).map((heading: string, index: number) => (
<Typography key={index} variant="caption" sx={{ display: 'block', mb: 0.5 }}>
• {heading}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{headings.h1_count}
</Typography>
))}
{detailedAnalysis?.heading_structure?.h2_headings && detailedAnalysis.heading_structure.h2_headings.length > 3 && (
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
... and {detailedAnalysis.heading_structure.h2_headings.length - 3} more
</Typography>
)}
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, background: 'rgba(156, 39, 176, 0.1)', borderRadius: 2, border: '1px solid rgba(156, 39, 176, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'secondary.main', mb: 1 }}>
H3 Headings ({detailedAnalysis?.heading_structure?.h3_count || 0})
</Typography>
{detailedAnalysis?.heading_structure?.h3_headings?.slice(0, 3).map((heading: string, index: number) => (
<Typography key={index} variant="caption" sx={{ display: 'block', mb: 0.5 }}>
• {heading}
</Typography>
))}
{detailedAnalysis?.heading_structure?.h3_headings && detailedAnalysis.heading_structure.h3_headings.length > 3 && (
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
... and {detailedAnalysis.heading_structure.h3_headings.length - 3} more
</Typography>
)}
</Box>
</Grid>
</Grid>
<Box sx={{ mt: 2, p: 2, background: 'rgba(0,0,0,0.02)', borderRadius: 2 }}>
<Tooltip
title={
<Box sx={{ p: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Heading Hierarchy Score
</Typography>
<Typography variant="body2" sx={{ mb: 1 }}>
Score (0-100) indicating how well your heading structure follows SEO best practices.
</Typography>
<Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
<strong>Scoring Factors:</strong> H1 presence, logical hierarchy, keyword usage in headings
</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>
<strong>Why it matters:</strong> Good heading structure helps search engines understand your content and improves readability.
<Typography variant="caption" sx={{ color: '#64748b' }}>
{headings.h1_headings?.[0] ? `Primary: ${headings.h1_headings[0]}` : 'Primary heading analysis'}
</Typography>
</Box>
}
arrow
>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1, cursor: 'help' }}>
Heading Hierarchy Score: {detailedAnalysis?.heading_structure?.heading_hierarchy_score || 'N/A'}
</Typography>
</Tooltip>
</Box>
{/* Structure Recommendations */}
{detailedAnalysis?.content_structure?.recommendations && detailedAnalysis.content_structure.recommendations.length > 0 && (
<Box sx={{ mt: 2, p: 2, background: 'rgba(255, 193, 7, 0.1)', borderRadius: 2, border: '1px solid rgba(255, 193, 7, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: 'warning.main' }}>
Structure Recommendations
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{detailedAnalysis.content_structure.recommendations.map((recommendation: string, index: number) => (
<Typography key={index} variant="caption" sx={{ display: 'block' }}>
• {recommendation}
</Grid>
<Grid item xs={12} md={4}>
<Box sx={highlightCard('rgba(34,197,94,0.45)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#15803d', mb: 1 }}>
H2 Headings
</Typography>
))}
</Box>
</Box>
)}
{/* Heading Recommendations */}
{detailedAnalysis?.heading_structure?.recommendations && detailedAnalysis.heading_structure.recommendations.length > 0 && (
<Box sx={{ mt: 2, p: 2, background: 'rgba(33, 150, 243, 0.1)', borderRadius: 2, border: '1px solid rgba(33, 150, 243, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: 'primary.main' }}>
Heading Recommendations
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{detailedAnalysis.heading_structure.recommendations.map((recommendation: string, index: number) => (
<Typography key={index} variant="caption" sx={{ display: 'block' }}>
• {recommendation}
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{headings.h2_count}
</Typography>
))}
</Box>
</Box>
)}
{/* Content Quality Recommendations */}
{detailedAnalysis?.content_quality?.recommendations && detailedAnalysis.content_quality.recommendations.length > 0 && (
<Box sx={{ mt: 2, p: 2, background: 'rgba(76, 175, 80, 0.1)', borderRadius: 2, border: '1px solid rgba(76, 175, 80, 0.3)' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: 'success.main' }}>
Content Quality Recommendations
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{detailedAnalysis.content_quality.recommendations.map((recommendation: string, index: number) => (
<Typography key={index} variant="caption" sx={{ display: 'block' }}>
{recommendation}
<Typography variant="caption" sx={{ color: '#64748b' }}>
{headings.h2_headings?.slice(0, 2).join(', ') || 'Summary of subtopics'}
</Typography>
))}
</Box>
</Box>
)}
</Paper>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={highlightCard('rgba(14,165,233,0.45)')}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#0ea5e9', mb: 1 }}>
H3 Headings
</Typography>
<Typography variant="h6" sx={{ fontWeight: 800, color: '#0f172a' }}>
{headings.h3_count}
</Typography>
<Typography variant="caption" sx={{ color: '#64748b' }}>
{headings.h3_headings?.slice(0, 2).join(', ') || 'Supportive outline points'}
</Typography>
</Box>
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
</Grid>
)}
</Box>
);
};