Blog SEO Analysis Modal - Updated with SEO Metadata Generator, Core Metadata Tab, and Metadata Display Components

This commit is contained in:
ajaysi
2025-09-23 16:21:09 +05:30
parent 12119d418b
commit a91677782e
16 changed files with 3433 additions and 89 deletions

View File

@@ -0,0 +1,394 @@
/**
* Core Metadata Tab Component
*
* Displays and allows editing of core SEO metadata including:
* - SEO Title
* - Meta Description
* - URL Slug
* - Blog Tags
* - Blog Categories
* - Social Hashtags
* - Reading Time
* - Focus Keyword
*/
import React from 'react';
import {
Box,
Typography,
TextField,
Chip,
Paper,
Grid,
IconButton,
Tooltip,
InputAdornment,
FormControl,
InputLabel,
Select,
MenuItem,
OutlinedInput,
Alert
} from '@mui/material';
import {
ContentCopy as CopyIcon,
Check as CheckIcon,
Search as SearchIcon,
Link as LinkIcon,
Tag as TagIcon,
Category as CategoryIcon,
Schedule as ScheduleIcon,
TrendingUp as TrendingUpIcon
} from '@mui/icons-material';
interface CoreMetadataTabProps {
metadata: any;
onMetadataEdit: (field: string, value: any) => void;
onCopyToClipboard: (text: string, itemId: string) => void;
copiedItems: Set<string>;
}
export const CoreMetadataTab: React.FC<CoreMetadataTabProps> = ({
metadata,
onMetadataEdit,
onCopyToClipboard,
copiedItems
}) => {
const handleTextFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
onMetadataEdit(field, event.target.value);
};
const handleTagsChange = (field: string) => (event: any) => {
const value = typeof event.target.value === 'string' ? event.target.value.split(',') : event.target.value;
onMetadataEdit(field, value);
};
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}`;
};
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<SearchIcon sx={{ color: 'primary.main' }} />
Core SEO Metadata
</Typography>
<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)' }}>
<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 }} />
SEO Title
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(metadata.seo_title || '', 'seo_title')}
>
{copiedItems.has('seo_title') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
multiline
rows={2}
value={metadata.seo_title || ''}
onChange={handleTextFieldChange('seo_title')}
placeholder="Enter SEO-optimized title (50-60 characters)"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((metadata.seo_title || '').length, 60)}
>
{getCharacterCountText((metadata.seo_title || '').length, 60)}
</Typography>
</InputAdornment>
)
}}
/>
<Alert severity="info" sx={{ mt: 1 }}>
Include your primary keyword and make it compelling for clicks
</Alert>
</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)' }}>
<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 }} />
Meta Description
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(metadata.meta_description || '', 'meta_description')}
>
{copiedItems.has('meta_description') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
multiline
rows={3}
value={metadata.meta_description || ''}
onChange={handleTextFieldChange('meta_description')}
placeholder="Enter compelling meta description (150-160 characters)"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((metadata.meta_description || '').length, 160)}
>
{getCharacterCountText((metadata.meta_description || '').length, 160)}
</Typography>
</InputAdornment>
)
}}
/>
<Alert severity="info" sx={{ mt: 1 }}>
Include a call-to-action and your primary keyword
</Alert>
</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)' }}>
<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 }} />
URL Slug
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(metadata.url_slug || '', 'url_slug')}
>
{copiedItems.has('url_slug') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={metadata.url_slug || ''}
onChange={handleTextFieldChange('url_slug')}
placeholder="seo-friendly-url-slug"
helperText="Use lowercase letters, numbers, and hyphens only"
/>
</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)' }}>
<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 }} />
Focus Keyword
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(metadata.focus_keyword || '', 'focus_keyword')}
>
{copiedItems.has('focus_keyword') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={metadata.focus_keyword || ''}
onChange={handleTextFieldChange('focus_keyword')}
placeholder="primary-keyword"
helperText="Your main SEO keyword for this post"
/>
</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)' }}>
<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 }} />
Blog Tags
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard((metadata.blog_tags || []).join(', '), 'blog_tags')}
>
{copiedItems.has('blog_tags') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<FormControl fullWidth>
<InputLabel>Tags</InputLabel>
<Select
multiple
value={metadata.blog_tags || []}
onChange={handleTagsChange('blog_tags')}
input={<OutlinedInput label="Tags" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
<Chip key={value} label={value} size="small" />
))}
</Box>
)}
>
{(metadata.blog_tags || []).map((tag: string) => (
<MenuItem key={tag} value={tag}>
{tag}
</MenuItem>
))}
</Select>
</FormControl>
<Alert severity="info" sx={{ mt: 1 }}>
Add relevant tags for better categorization and discoverability
</Alert>
</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)' }}>
<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 }} />
Blog Categories
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard((metadata.blog_categories || []).join(', '), 'blog_categories')}
>
{copiedItems.has('blog_categories') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<FormControl fullWidth>
<InputLabel>Categories</InputLabel>
<Select
multiple
value={metadata.blog_categories || []}
onChange={handleTagsChange('blog_categories')}
input={<OutlinedInput label="Categories" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
<Chip key={value} label={value} size="small" color="primary" />
))}
</Box>
)}
>
{(metadata.blog_categories || []).map((category: string) => (
<MenuItem key={category} value={category}>
{category}
</MenuItem>
))}
</Select>
</FormControl>
<Alert severity="info" sx={{ mt: 1 }}>
Select 2-3 primary categories for your content
</Alert>
</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)' }}>
<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 }} />
Social Hashtags
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard((metadata.social_hashtags || []).join(' '), 'social_hashtags')}
>
{copiedItems.has('social_hashtags') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<FormControl fullWidth>
<InputLabel>Hashtags</InputLabel>
<Select
multiple
value={metadata.social_hashtags || []}
onChange={handleTagsChange('social_hashtags')}
input={<OutlinedInput label="Hashtags" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
<Chip key={value} label={value} size="small" color="secondary" />
))}
</Box>
)}
>
{(metadata.social_hashtags || []).map((hashtag: string) => (
<MenuItem key={hashtag} value={hashtag}>
{hashtag}
</MenuItem>
))}
</Select>
</FormControl>
<Alert severity="info" sx={{ mt: 1 }}>
Include # symbol for social media platforms
</Alert>
</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)' }}>
<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 }} />
Reading Time
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(`${metadata.reading_time || 0} minutes`, 'reading_time')}
>
{copiedItems.has('reading_time') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
type="number"
value={metadata.reading_time || 0}
onChange={handleTextFieldChange('reading_time')}
placeholder="5"
InputProps={{
endAdornment: <InputAdornment position="end">minutes</InputAdornment>
}}
helperText="Estimated reading time for your content"
/>
</Paper>
</Grid>
</Grid>
</Box>
);
};

View File

@@ -0,0 +1,388 @@
/**
* Preview Card Component
*
* Displays live previews of how the metadata will appear in:
* - Search engine results
* - Social media platforms
* - Rich snippets
*/
import React from 'react';
import {
Box,
Typography,
Paper,
Grid,
Card,
CardContent,
Chip,
Divider,
Alert,
IconButton,
Tooltip,
Button
} from '@mui/material';
import {
ContentCopy as CopyIcon,
Check as CheckIcon,
Search as SearchIcon,
Share as ShareIcon,
Code as CodeIcon,
Facebook as FacebookIcon,
Twitter as TwitterIcon,
LinkedIn as LinkedInIcon,
Google as GoogleIcon
} from '@mui/icons-material';
interface PreviewCardProps {
metadata: any;
blogTitle: string;
}
export const PreviewCard: React.FC<PreviewCardProps> = ({
metadata,
blogTitle
}) => {
const copyToClipboard = async (text: string, itemId: string) => {
try {
await navigator.clipboard.writeText(text);
// You could add a state to show "Copied!" feedback here
} catch (err) {
console.error('Failed to copy to clipboard:', err);
}
};
const getCurrentDate = () => {
return new Date().toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};
const getCurrentTime = () => {
return new Date().toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
};
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<SearchIcon sx={{ color: 'primary.main' }} />
Live Preview
</Typography>
<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
</Typography>
<Chip label="SERP Preview" size="small" color="primary" />
</Box>
<Card sx={{ border: '1px solid #e0e0e0', boxShadow: 'none' }}>
<CardContent sx={{ p: 2 }}>
{/* URL */}
<Typography variant="caption" sx={{ color: '#1a0dab', mb: 1, display: 'block' }}>
{metadata.canonical_url || 'https://yourwebsite.com/blog-post'}
</Typography>
{/* Title */}
<Typography
variant="h6"
sx={{
color: '#1a0dab',
fontWeight: 400,
fontSize: '1.1rem',
lineHeight: 1.3,
mb: 1,
cursor: 'pointer',
'&:hover': { textDecoration: 'underline' }
}}
>
{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>
{/* 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...'}
</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>
{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:
</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
</Typography>
</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>
</Box>
);
};

View File

@@ -0,0 +1,444 @@
/**
* Social Media Tab Component
*
* Displays and allows editing of social media metadata including:
* - Open Graph tags (Facebook, LinkedIn)
* - Twitter Card tags
* - Social media previews
*/
import React from 'react';
import {
Box,
Typography,
TextField,
Paper,
Grid,
IconButton,
Tooltip,
InputAdornment,
Alert,
Card,
CardContent,
Divider,
Chip
} from '@mui/material';
import {
ContentCopy as CopyIcon,
Check as CheckIcon,
Share as ShareIcon,
Facebook as FacebookIcon,
Twitter as TwitterIcon,
LinkedIn as LinkedInIcon,
Image as ImageIcon,
Link as LinkIcon
} from '@mui/icons-material';
interface SocialMediaTabProps {
metadata: any;
onMetadataEdit: (field: string, value: any) => void;
onCopyToClipboard: (text: string, itemId: string) => void;
copiedItems: Set<string>;
}
export const SocialMediaTab: React.FC<SocialMediaTabProps> = ({
metadata,
onMetadataEdit,
onCopyToClipboard,
copiedItems
}) => {
const handleTextFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
onMetadataEdit(field, event.target.value);
};
const handleNestedFieldChange = (parentField: string, childField: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
const currentValue = metadata[parentField] || {};
onMetadataEdit(parentField, {
...currentValue,
[childField]: event.target.value
});
};
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 openGraph = metadata.open_graph || {};
const twitterCard = metadata.twitter_card || {};
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<ShareIcon sx={{ color: 'primary.main' }} />
Social Media Metadata
</Typography>
<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)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<FacebookIcon sx={{ color: '#1877F2' }} />
<LinkedInIcon sx={{ color: '#0077B5' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Open Graph Tags
</Typography>
<Chip label="Facebook & LinkedIn" size="small" color="primary" />
</Box>
<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 }}>
OG Title
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(openGraph.title || '', 'og_title')}
>
{copiedItems.has('og_title') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={openGraph.title || ''}
onChange={handleNestedFieldChange('open_graph', 'title')}
placeholder="Open Graph title (60 characters max)"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((openGraph.title || '').length, 60)}
>
{getCharacterCountText((openGraph.title || '').length, 60)}
</Typography>
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
OG Description
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(openGraph.description || '', 'og_description')}
>
{copiedItems.has('og_description') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
multiline
rows={2}
value={openGraph.description || ''}
onChange={handleNestedFieldChange('open_graph', 'description')}
placeholder="Open Graph description (160 characters max)"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((openGraph.description || '').length, 160)}
>
{getCharacterCountText((openGraph.description || '').length, 160)}
</Typography>
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
OG Image URL
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(openGraph.image || '', 'og_image')}
>
{copiedItems.has('og_image') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={openGraph.image || ''}
onChange={handleNestedFieldChange('open_graph', 'image')}
placeholder="https://example.com/image.jpg"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ImageIcon />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
OG URL
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(openGraph.url || '', 'og_url')}
>
{copiedItems.has('og_url') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={openGraph.url || ''}
onChange={handleNestedFieldChange('open_graph', 'url')}
placeholder="https://example.com/blog-post"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LinkIcon />
</InputAdornment>
)
}}
/>
</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>
</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)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
<TwitterIcon sx={{ color: '#1DA1F2' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Twitter Card Tags
</Typography>
<Chip label="Twitter & X" size="small" color="info" />
</Box>
<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 }}>
Twitter Title
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(twitterCard.title || '', 'twitter_title')}
>
{copiedItems.has('twitter_title') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={twitterCard.title || ''}
onChange={handleNestedFieldChange('twitter_card', 'title')}
placeholder="Twitter card title (70 characters max)"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((twitterCard.title || '').length, 70)}
>
{getCharacterCountText((twitterCard.title || '').length, 70)}
</Typography>
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Twitter Description
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(twitterCard.description || '', 'twitter_description')}
>
{copiedItems.has('twitter_description') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
multiline
rows={2}
value={twitterCard.description || ''}
onChange={handleNestedFieldChange('twitter_card', 'description')}
placeholder="Twitter card description (200 characters max)"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Typography
variant="caption"
color={getCharacterCountColor((twitterCard.description || '').length, 200)}
>
{getCharacterCountText((twitterCard.description || '').length, 200)}
</Typography>
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Twitter Image URL
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(twitterCard.image || '', 'twitter_image')}
>
{copiedItems.has('twitter_image') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={twitterCard.image || ''}
onChange={handleNestedFieldChange('twitter_card', 'image')}
placeholder="https://example.com/twitter-image.jpg"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ImageIcon />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Twitter Site Handle
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(twitterCard.site || '', 'twitter_site')}
>
{copiedItems.has('twitter_site') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={twitterCard.site || ''}
onChange={handleNestedFieldChange('twitter_card', 'site')}
placeholder="@yourwebsite"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<TwitterIcon />
</InputAdornment>
)
}}
/>
</Grid>
</Grid>
<Alert severity="info" sx={{ mt: 2 }}>
Twitter cards provide rich previews when your content is shared on Twitter/X
</Alert>
</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 }}>
<ShareIcon />
Social Media Preview
</Typography>
<Grid container spacing={2}>
{/* Facebook Preview */}
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #e0e0e0' }}>
<CardContent sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<FacebookIcon sx={{ color: '#1877F2' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Facebook Preview
</Typography>
</Box>
<Box sx={{ border: '1px solid #e0e0e0', borderRadius: 1, p: 2, bgcolor: '#f5f5f5' }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
{openGraph.title || 'Your Blog Title'}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary', mb: 1, display: 'block' }}>
{openGraph.url || 'yourwebsite.com'}
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.875rem' }}>
{openGraph.description || 'Your meta description will appear here...'}
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
{/* Twitter Preview */}
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #e0e0e0' }}>
<CardContent sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<TwitterIcon sx={{ color: '#1DA1F2' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Twitter Preview
</Typography>
</Box>
<Box sx={{ border: '1px solid #e0e0e0', borderRadius: 1, p: 2, bgcolor: '#f5f5f5' }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
{twitterCard.title || 'Your Blog Title'}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary', mb: 1, display: 'block' }}>
{twitterCard.site || '@yourwebsite'}
</Typography>
<Typography variant="body2" sx={{ fontSize: '0.875rem' }}>
{twitterCard.description || 'Your Twitter description will appear here...'}
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
</Box>
);
};

View File

@@ -0,0 +1,509 @@
/**
* Structured Data Tab Component
*
* Displays and allows editing of JSON-LD structured data including:
* - Article schema
* - Author information
* - Publisher details
* - Publication dates
* - Keywords and categories
*/
import React, { useState } from 'react';
import {
Box,
Typography,
TextField,
Paper,
Grid,
IconButton,
Tooltip,
InputAdornment,
Alert,
Card,
CardContent,
Divider,
Chip,
Accordion,
AccordionSummary,
AccordionDetails,
Button
} from '@mui/material';
import {
ContentCopy as CopyIcon,
Check as CheckIcon,
Code as CodeIcon,
Person as PersonIcon,
Business as BusinessIcon,
CalendarToday as CalendarIcon,
ExpandMore as ExpandMoreIcon,
Visibility as VisibilityIcon,
Edit as EditIcon
} from '@mui/icons-material';
interface StructuredDataTabProps {
metadata: any;
onMetadataEdit: (field: string, value: any) => void;
onCopyToClipboard: (text: string, itemId: string) => void;
copiedItems: Set<string>;
}
export const StructuredDataTab: React.FC<StructuredDataTabProps> = ({
metadata,
onMetadataEdit,
onCopyToClipboard,
copiedItems
}) => {
const [showRawJson, setShowRawJson] = useState(false);
const handleTextFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
onMetadataEdit(field, event.target.value);
};
const handleNestedFieldChange = (parentField: string, childField: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
const currentValue = metadata[parentField] || {};
onMetadataEdit(parentField, {
...currentValue,
[childField]: event.target.value
});
};
const handleAuthorFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
const currentSchema = metadata.json_ld_schema || {};
const currentAuthor = currentSchema.author || {};
onMetadataEdit('json_ld_schema', {
...currentSchema,
author: {
...currentAuthor,
[field]: event.target.value
}
});
};
const handlePublisherFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
const currentSchema = metadata.json_ld_schema || {};
const currentPublisher = currentSchema.publisher || {};
onMetadataEdit('json_ld_schema', {
...currentSchema,
publisher: {
...currentPublisher,
[field]: event.target.value
}
});
};
const handleSchemaFieldChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
const currentSchema = metadata.json_ld_schema || {};
onMetadataEdit('json_ld_schema', {
...currentSchema,
[field]: event.target.value
});
};
const getJsonLdSchema = () => {
const schema = metadata.json_ld_schema || {};
return JSON.stringify(schema, null, 2);
};
const copyJsonLdSchema = () => {
onCopyToClipboard(getJsonLdSchema(), 'json_ld_schema');
};
const jsonLdSchema = metadata.json_ld_schema || {};
const author = jsonLdSchema.author || {};
const publisher = jsonLdSchema.publisher || {};
return (
<Box>
<Typography variant="h6" sx={{ mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon sx={{ color: 'primary.main' }} />
Structured Data (JSON-LD)
</Typography>
<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)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon />
Article Schema
</Typography>
<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 }}>
Headline
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(jsonLdSchema.headline || '', 'schema_headline')}
>
{copiedItems.has('schema_headline') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={jsonLdSchema.headline || ''}
onChange={handleSchemaFieldChange('headline')}
placeholder="Article headline"
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Description
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(jsonLdSchema.description || '', 'schema_description')}
>
{copiedItems.has('schema_description') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
multiline
rows={2}
value={jsonLdSchema.description || ''}
onChange={handleSchemaFieldChange('description')}
placeholder="Article description"
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Main Entity URL
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(jsonLdSchema.mainEntityOfPage || '', 'schema_url')}
>
{copiedItems.has('schema_url') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={jsonLdSchema.mainEntityOfPage || ''}
onChange={handleSchemaFieldChange('mainEntityOfPage')}
placeholder="https://example.com/blog-post"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<CodeIcon />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Word Count
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(jsonLdSchema.wordCount?.toString() || '', 'schema_wordcount')}
>
{copiedItems.has('schema_wordcount') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
type="number"
value={jsonLdSchema.wordCount || ''}
onChange={handleSchemaFieldChange('wordCount')}
placeholder="1500"
InputProps={{
endAdornment: <InputAdornment position="end">words</InputAdornment>
}}
/>
</Grid>
</Grid>
</Paper>
</Grid>
{/* 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)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<PersonIcon />
Author Information
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Author Name
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(author.name || '', 'author_name')}
>
{copiedItems.has('author_name') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={author.name || ''}
onChange={handleAuthorFieldChange('name')}
placeholder="Author Name"
/>
</Grid>
<Grid item xs={12}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Author Type
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(author['@type'] || '', 'author_type')}
>
{copiedItems.has('author_type') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={author['@type'] || ''}
onChange={handleAuthorFieldChange('@type')}
placeholder="Person"
/>
</Grid>
</Grid>
</Paper>
</Grid>
{/* 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)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<BusinessIcon />
Publisher Information
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Publisher Name
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(publisher.name || '', 'publisher_name')}
>
{copiedItems.has('publisher_name') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={publisher.name || ''}
onChange={handlePublisherFieldChange('name')}
placeholder="Publisher Name"
/>
</Grid>
<Grid item xs={12}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Publisher Logo
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(publisher.logo || '', 'publisher_logo')}
>
{copiedItems.has('publisher_logo') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
value={publisher.logo || ''}
onChange={handlePublisherFieldChange('logo')}
placeholder="https://example.com/logo.png"
/>
</Grid>
</Grid>
</Paper>
</Grid>
{/* 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)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CalendarIcon />
Publication Dates
</Typography>
<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 }}>
Date Published
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(jsonLdSchema.datePublished || '', 'date_published')}
>
{copiedItems.has('date_published') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
type="datetime-local"
value={jsonLdSchema.datePublished || ''}
onChange={handleSchemaFieldChange('datePublished')}
InputLabelProps={{ shrink: true }}
/>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Date Modified
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard(jsonLdSchema.dateModified || '', 'date_modified')}
>
{copiedItems.has('date_modified') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
type="datetime-local"
value={jsonLdSchema.dateModified || ''}
onChange={handleSchemaFieldChange('dateModified')}
InputLabelProps={{ shrink: true }}
/>
</Grid>
</Grid>
</Paper>
</Grid>
{/* 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)' }}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon />
Keywords & Categories
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Keywords
</Typography>
<Tooltip title="Copy to clipboard">
<IconButton
size="small"
onClick={() => onCopyToClipboard((jsonLdSchema.keywords || []).join(', '), 'schema_keywords')}
>
{copiedItems.has('schema_keywords') ? <CheckIcon color="success" /> : <CopyIcon />}
</IconButton>
</Tooltip>
</Box>
<TextField
fullWidth
multiline
rows={2}
value={(jsonLdSchema.keywords || []).join(', ')}
onChange={(e) => {
const keywords = e.target.value.split(',').map(k => k.trim()).filter(k => k);
handleSchemaFieldChange('keywords')({ target: { value: keywords } } as any);
}}
placeholder="keyword1, keyword2, keyword3"
helperText="Separate keywords with commas"
/>
</Grid>
</Grid>
</Paper>
</Grid>
{/* Raw JSON View */}
<Grid item xs={12}>
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CodeIcon />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Raw JSON-LD Schema
</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ position: 'relative' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
Complete JSON-LD Schema
</Typography>
<Button
variant="outlined"
size="small"
startIcon={copiedItems.has('json_ld_schema') ? <CheckIcon /> : <CopyIcon />}
onClick={copyJsonLdSchema}
>
{copiedItems.has('json_ld_schema') ? 'Copied!' : 'Copy JSON'}
</Button>
</Box>
<TextField
fullWidth
multiline
rows={15}
value={getJsonLdSchema()}
InputProps={{
readOnly: true,
sx: {
fontFamily: 'monospace',
fontSize: '0.875rem'
}
}}
sx={{
'& .MuiInputBase-input': {
fontFamily: 'monospace',
fontSize: '0.875rem'
}
}}
/>
</Box>
</AccordionDetails>
</Accordion>
</Grid>
{/* Information Alert */}
<Grid item xs={12}>
<Alert severity="info">
<Typography variant="body2">
<strong>JSON-LD Structured Data:</strong> This schema helps search engines understand your content
and may enable rich snippets in search results. The data follows Schema.org Article guidelines.
</Typography>
</Alert>
</Grid>
</Grid>
</Box>
);
};