Added image generation to blog writer
This commit is contained in:
@@ -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 (<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 (>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>
|
||||
|
||||
@@ -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 50–60 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 150–160 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 3–6 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 1–3 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). 3–5 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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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={{
|
||||
|
||||
264
frontend/src/components/BlogWriter/SEO/OverallScoreCard.tsx
Normal file
264
frontend/src/components/BlogWriter/SEO/OverallScoreCard.tsx
Normal 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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user