Added image generation to blog writer

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

View File

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

View File

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

View File

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

View File

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