final section 10 -- and lgoabl styles adn compoeents plan

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-01 10:41:16 +00:00
parent 41e124d8e8
commit d389576634
18 changed files with 1918 additions and 1374 deletions

View File

@@ -1,24 +1,20 @@
/**
* BulkWordPressPublish Component
*
* Handles bulk publishing multiple content items to connected WordPress sites.
*
* 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md
*/
import React, { useState } from 'react';
import { Button } from '../ui/button/Button';
import { Modal } from '../ui/modal';
import Alert from '../ui/alert/Alert';
import { Spinner } from '../ui/spinner/Spinner';
import {
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Typography,
Alert,
Box,
CircularProgress,
List,
ListItem,
ListItemText,
Divider
} from '@mui/material';
import {
Publish as PublishIcon,
CheckCircle as SuccessIcon,
Error as ErrorIcon
} from '@mui/icons-material';
CheckCircleIcon,
ErrorIcon,
PaperPlaneIcon,
} from '../../icons';
import { api } from '../../services/api';
interface BulkWordPressPublishProps {
@@ -121,140 +117,140 @@ export const BulkWordPressPublish: React.FC<BulkWordPressPublishProps> = ({
return (
<>
<Button
variant="contained"
color="primary"
startIcon={<PublishIcon />}
variant="primary"
tone="brand"
startIcon={<PaperPlaneIcon className="h-4 w-4" />}
onClick={() => setOpen(true)}
size="small"
size="sm"
>
Publish Ready ({readyToPublish.length})
</Button>
<Dialog
open={open}
<Modal
isOpen={open}
onClose={handleClose}
maxWidth="md"
fullWidth
disableEscapeKeyDown={publishing}
className="max-w-2xl p-6"
>
<DialogTitle>
Bulk Publish to Site
</DialogTitle>
<DialogContent>
<div className="space-y-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Bulk Publish to Site
</h2>
{!publishing && results.length === 0 && (
<>
<Typography variant="body1" gutterBottom>
<p className="text-gray-700 dark:text-gray-300">
Ready to publish <strong>{readyToPublish.length}</strong> content items to your site:
</Typography>
</p>
<Alert severity="info" sx={{ mt: 2, mb: 2 }}>
Only content with generated images and not yet published will be included.
</Alert>
<Alert
variant="info"
title="Info"
message="Only content with generated images and not yet published will be included."
/>
<Box sx={{ maxHeight: 300, overflow: 'auto', border: 1, borderColor: 'divider', borderRadius: 1 }}>
<List dense>
{readyToPublish.map((item, index) => (
<div key={item.id}>
<ListItem>
<ListItemText
primary={item.title}
secondary={`ID: ${item.id}`}
/>
</ListItem>
{index < readyToPublish.length - 1 && <Divider />}
<div className="max-h-72 overflow-auto rounded-lg border border-gray-200 dark:border-gray-700">
{readyToPublish.map((item, index) => (
<div key={item.id}>
<div className="px-4 py-3">
<p className="font-medium text-gray-900 dark:text-white">{item.title}</p>
<p className="text-sm text-gray-500 dark:text-gray-400">ID: {item.id}</p>
</div>
))}
</List>
</Box>
{index < readyToPublish.length - 1 && (
<div className="border-t border-gray-200 dark:border-gray-700" />
)}
</div>
))}
</div>
</>
)}
{publishing && (
<Box display="flex" alignItems="center" gap={2} py={4}>
<CircularProgress />
<Typography>
<div className="flex items-center gap-3 py-8">
<Spinner size="md" />
<p className="text-gray-700 dark:text-gray-300">
Publishing {readyToPublish.length} items to WordPress...
</Typography>
</Box>
</p>
</div>
)}
{!publishing && results.length > 0 && (
<>
<Box sx={{ mb: 2 }}>
<div className="space-y-2">
{successCount > 0 && (
<Alert severity="success" sx={{ mb: 1 }}>
Successfully published {successCount} items
</Alert>
<Alert
variant="success"
title="Published"
message={`Successfully published ${successCount} items`}
/>
)}
{failedCount > 0 && (
<Alert severity="error">
Failed to publish {failedCount} items
</Alert>
<Alert
variant="error"
title="Failed"
message={`Failed to publish ${failedCount} items`}
/>
)}
</Box>
</div>
<Typography variant="h6" gutterBottom>
Results:
</Typography>
<h3 className="text-lg font-medium text-gray-900 dark:text-white">Results:</h3>
<Box sx={{ maxHeight: 400, overflow: 'auto', border: 1, borderColor: 'divider', borderRadius: 1 }}>
<List dense>
{results.map((result, index) => (
<div key={result.id}>
<ListItem>
<Box display="flex" alignItems="center" width="100%">
{result.status === 'success' ? (
<SuccessIcon color="success" sx={{ mr: 1 }} />
) : (
<ErrorIcon color="error" sx={{ mr: 1 }} />
)}
<ListItemText
primary={result.title}
secondary={result.message || (result.status === 'success' ? 'Published successfully' : 'Publishing failed')}
/>
</Box>
</ListItem>
{index < results.length - 1 && <Divider />}
<div className="max-h-96 overflow-auto rounded-lg border border-gray-200 dark:border-gray-700">
{results.map((result, index) => (
<div key={result.id}>
<div className="flex items-center gap-3 px-4 py-3">
{result.status === 'success' ? (
<CheckCircleIcon className="h-5 w-5 text-success-500" />
) : (
<ErrorIcon className="h-5 w-5 text-error-500" />
)}
<div className="flex-1">
<p className="font-medium text-gray-900 dark:text-white">{result.title}</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
{result.message || (result.status === 'success' ? 'Published successfully' : 'Publishing failed')}
</p>
</div>
</div>
))}
</List>
</Box>
</>
)}
</DialogContent>
<DialogActions>
{!publishing && results.length === 0 && (
<>
<Button onClick={handleClose}>
Cancel
</Button>
<Button
onClick={handleBulkPublish}
variant="contained"
color="primary"
startIcon={<PublishIcon />}
>
Publish All ({readyToPublish.length})
</Button>
{index < results.length - 1 && (
<div className="border-t border-gray-200 dark:border-gray-700" />
)}
</div>
))}
</div>
</>
)}
{publishing && (
<Button disabled>
Publishing...
</Button>
)}
{!publishing && results.length > 0 && (
<Button onClick={handleClose} variant="contained">
Close
</Button>
)}
</DialogActions>
</Dialog>
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
{!publishing && results.length === 0 && (
<>
<Button variant="outline" tone="neutral" onClick={handleClose}>
Cancel
</Button>
<Button
variant="primary"
tone="brand"
onClick={handleBulkPublish}
startIcon={<PaperPlaneIcon className="h-4 w-4" />}
>
Publish All ({readyToPublish.length})
</Button>
</>
)}
{publishing && (
<Button variant="outline" tone="neutral" disabled>
Publishing...
</Button>
)}
{!publishing && results.length > 0 && (
<Button variant="primary" tone="brand" onClick={handleClose}>
Close
</Button>
)}
</div>
</div>
</Modal>
</>
);
};

View File

@@ -1,20 +1,21 @@
import React, { useState } from 'react';
/**
* ContentActionsMenu Component
*
* Dropdown menu for content actions (edit, generate images, export, delete, publish).
*
* 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md
*/
import React, { useState, useRef } from 'react';
import { Button } from '../ui/button/Button';
import { Dropdown } from '../ui/dropdown/Dropdown';
import {
IconButton,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
Divider
} from '@mui/material';
import {
MoreVert as MoreVertIcon,
Publish as PublishIcon,
Edit as EditIcon,
Image as ImageIcon,
GetApp as ExportIcon,
Delete as DeleteIcon
} from '@mui/icons-material';
MoreDotIcon,
PaperPlaneIcon,
PencilIcon,
FileIcon,
DownloadIcon,
TrashBinIcon,
} from '../../icons';
import { WordPressPublish } from './WordPressPublish';
interface ContentActionsMenuProps {
@@ -40,16 +41,12 @@ export const ContentActionsMenu: React.FC<ContentActionsMenuProps> = ({
onDelete,
onWordPressStatusChange
}) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [showWordPressDialog, setShowWordPressDialog] = useState(false);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const buttonRef = useRef<HTMLButtonElement>(null);
const handleClose = () => {
setAnchorEl(null);
setIsOpen(false);
};
const handlePublishClick = () => {
@@ -69,85 +66,85 @@ export const ContentActionsMenu: React.FC<ContentActionsMenuProps> = ({
return (
<>
<IconButton
<button
ref={buttonRef}
aria-label="more actions"
id="content-actions-button"
aria-controls={open ? 'content-actions-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
size="small"
onClick={() => setIsOpen(!isOpen)}
className="dropdown-toggle flex h-8 w-8 items-center justify-center rounded-lg text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
>
<MoreVertIcon />
</IconButton>
<MoreDotIcon className="h-5 w-5" />
</button>
<Menu
id="content-actions-menu"
anchorEl={anchorEl}
open={open}
<Dropdown
isOpen={isOpen}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'content-actions-button',
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
anchorRef={buttonRef as React.RefObject<HTMLElement>}
placement="bottom-right"
className="w-48"
>
{/* WordPress Publishing - Only show if images are ready */}
{canPublishToWordPress && (
<>
<MenuItem onClick={handlePublishClick}>
<ListItemIcon>
<PublishIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Publish to Site</ListItemText>
</MenuItem>
<Divider />
</>
)}
{/* Edit Action */}
{onEdit && (
<MenuItem onClick={() => handleMenuAction(onEdit)}>
<ListItemIcon>
<EditIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Edit</ListItemText>
</MenuItem>
)}
{/* Generate Image Action */}
{onGenerateImage && (
<MenuItem onClick={() => handleMenuAction(onGenerateImage)}>
<ListItemIcon>
<ImageIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Generate Image Prompts</ListItemText>
</MenuItem>
)}
{/* Export Action */}
{onExport && (
<MenuItem onClick={() => handleMenuAction(onExport)}>
<ListItemIcon>
<ExportIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Export</ListItemText>
</MenuItem>
)}
{/* Delete Action */}
{onDelete && (
<>
<Divider />
<MenuItem onClick={() => handleMenuAction(onDelete)} sx={{ color: 'error.main' }}>
<ListItemIcon>
<DeleteIcon fontSize="small" color="error" />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
</MenuItem>
</>
)}
</Menu>
<div className="py-1">
{/* WordPress Publishing - Only show if images are ready */}
{canPublishToWordPress && (
<>
<button
onClick={handlePublishClick}
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
<PaperPlaneIcon className="h-4 w-4" />
<span>Publish to Site</span>
</button>
<div className="my-1 border-t border-gray-200 dark:border-gray-700" />
</>
)}
{/* Edit Action */}
{onEdit && (
<button
onClick={() => handleMenuAction(onEdit)}
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
<PencilIcon className="h-4 w-4" />
<span>Edit</span>
</button>
)}
{/* Generate Image Action */}
{onGenerateImage && (
<button
onClick={() => handleMenuAction(onGenerateImage)}
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
<FileIcon className="h-4 w-4" />
<span>Generate Image Prompts</span>
</button>
)}
{/* Export Action */}
{onExport && (
<button
onClick={() => handleMenuAction(onExport)}
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
<DownloadIcon className="h-4 w-4" />
<span>Export</span>
</button>
)}
{/* Delete Action */}
{onDelete && (
<>
<div className="my-1 border-t border-gray-200 dark:border-gray-700" />
<button
onClick={() => handleMenuAction(onDelete)}
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-error-600 hover:bg-error-50 dark:text-error-400 dark:hover:bg-error-500/10"
>
<TrashBinIcon className="h-4 w-4" />
<span>Delete</span>
</button>
</>
)}
</div>
</Dropdown>
{/* WordPress Publish Dialog */}
{showWordPressDialog && (

View File

@@ -1,26 +1,23 @@
/**
* WordPressPublish Component
*
* Handles publishing content to connected WordPress sites.
*
* 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md
*/
import React, { useState, useEffect } from 'react';
import {
Button,
Alert,
Chip,
IconButton,
Tooltip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
CircularProgress,
Box,
Typography
} from '@mui/material';
import {
Publish as PublishIcon,
Refresh as RefreshIcon,
CheckCircle as SuccessIcon,
Error as ErrorIcon,
Schedule as PendingIcon,
Sync as SyncingIcon
} from '@mui/icons-material';
import { Button } from '../ui/button/Button';
import { Modal } from '../ui/modal';
import Alert from '../ui/alert/Alert';
import { Badge } from '../ui/badge/Badge';
import { Tooltip } from '../ui/tooltip/Tooltip';
import { Spinner } from '../ui/spinner/Spinner';
import {
CheckCircleIcon,
ErrorIcon,
TimeIcon,
PaperPlaneIcon,
} from '../../icons';
import { api } from '../../services/api';
export interface WordPressPublishProps {
@@ -149,8 +146,9 @@ export const WordPressPublish: React.FC<WordPressPublishProps> = ({
const getStatusInfo = () => {
if (!wpStatus) {
return {
color: 'default' as const,
icon: <PublishIcon />,
tone: 'neutral' as const,
badgeTone: 'neutral' as const,
icon: <PaperPlaneIcon className="h-4 w-4" />,
label: 'Not Published',
action: 'publish'
};
@@ -159,36 +157,41 @@ export const WordPressPublish: React.FC<WordPressPublishProps> = ({
switch (wpStatus.wordpress_sync_status) {
case 'pending':
return {
color: 'warning' as const,
icon: <PendingIcon />,
tone: 'warning' as const,
badgeTone: 'warning' as const,
icon: <TimeIcon className="h-4 w-4" />,
label: 'Queued',
action: 'wait'
};
case 'syncing':
return {
color: 'info' as const,
icon: <SyncingIcon className="animate-spin" />,
tone: 'brand' as const,
badgeTone: 'info' as const,
icon: <Spinner size="sm" />,
label: 'Publishing...',
action: 'wait'
};
case 'success':
return {
color: 'success' as const,
icon: <SuccessIcon />,
tone: 'success' as const,
badgeTone: 'success' as const,
icon: <CheckCircleIcon className="h-4 w-4" />,
label: 'Published',
action: 'view'
};
case 'failed':
return {
color: 'error' as const,
icon: <ErrorIcon />,
tone: 'danger' as const,
badgeTone: 'danger' as const,
icon: <ErrorIcon className="h-4 w-4" />,
label: 'Failed',
action: 'retry'
};
default:
return {
color: 'default' as const,
icon: <PublishIcon />,
tone: 'neutral' as const,
badgeTone: 'neutral' as const,
icon: <PaperPlaneIcon className="h-4 w-4" />,
label: 'Not Published',
action: 'publish'
};
@@ -202,181 +205,178 @@ export const WordPressPublish: React.FC<WordPressPublishProps> = ({
if (!shouldShowPublishButton) {
return (
<Tooltip title={`Images must be generated before publishing to site`}>
<Box display="flex" alignItems="center" gap={1}>
<Tooltip text="Images must be generated before publishing to site">
<div className="flex items-center gap-2">
<Button
variant="outlined"
variant="outline"
tone="neutral"
disabled
size={size}
startIcon={<PendingIcon />}
size={size === 'small' ? 'sm' : size === 'large' ? 'lg' : 'md'}
startIcon={<TimeIcon className="h-4 w-4" />}
>
Awaiting Images
</Button>
{size !== 'small' && (
<Chip
icon={<PendingIcon />}
label="Images Pending"
color="warning"
size="small"
variant="outlined"
/>
<Badge
tone="warning"
variant="outline"
startIcon={<TimeIcon className="h-3 w-3" />}
>
Images Pending
</Badge>
)}
</Box>
</div>
</Tooltip>
);
}
const renderButton = () => {
if (size === 'small') {
return (
<Tooltip title={`Site: ${statusInfo.label}`}>
<IconButton
size="small"
onClick={() => {
if (statusInfo.action === 'publish') {
setPublishDialogOpen(true);
} else if (statusInfo.action === 'retry') {
handleRetry();
} else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
disabled={loading || statusInfo.action === 'wait'}
color={statusInfo.color}
>
{loading ? <CircularProgress size={16} /> : statusInfo.icon}
</IconButton>
</Tooltip>
);
const handleButtonClick = () => {
if (statusInfo.action === 'publish') {
setPublishDialogOpen(true);
} else if (statusInfo.action === 'retry') {
handleRetry();
} else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
};
const renderButton = () => {
const buttonSize = size === 'small' ? 'sm' : size === 'large' ? 'lg' : 'md';
return (
<Button
variant={statusInfo.action === 'publish' ? 'contained' : 'outlined'}
color={statusInfo.color}
startIcon={loading ? <CircularProgress size={20} /> : statusInfo.icon}
onClick={() => {
if (statusInfo.action === 'publish') {
setPublishDialogOpen(true);
} else if (statusInfo.action === 'retry') {
handleRetry();
} else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
disabled={loading || statusInfo.action === 'wait'}
size={size}
>
{statusInfo.action === 'publish' && 'Publish to Site'}
{statusInfo.action === 'retry' && 'Retry'}
{statusInfo.action === 'view' && 'View on Site'}
{statusInfo.action === 'wait' && statusInfo.label}
</Button>
<Tooltip text={`Site: ${statusInfo.label}`}>
<Button
size={buttonSize}
variant={statusInfo.action === 'publish' ? 'primary' : 'outline'}
tone={statusInfo.tone}
onClick={handleButtonClick}
disabled={loading || statusInfo.action === 'wait'}
startIcon={loading ? <Spinner size="sm" /> : statusInfo.icon}
>
{statusInfo.action === 'publish' && 'Publish to Site'}
{statusInfo.action === 'retry' && 'Retry'}
{statusInfo.action === 'view' && 'View on Site'}
{statusInfo.action === 'wait' && statusInfo.label}
</Button>
</Tooltip>
);
};
const renderStatusChip = () => {
const renderStatusBadge = () => {
if (size === 'small') return null;
return (
<Chip
icon={statusInfo.icon}
label={statusInfo.label}
color={statusInfo.color}
size="small"
variant="outlined"
onClick={() => {
if (wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
style={{
marginLeft: 8,
cursor: wpStatus?.wordpress_post_url ? 'pointer' : 'default'
}}
/>
<Badge
tone={statusInfo.badgeTone}
variant="outline"
startIcon={statusInfo.icon}
className={wpStatus?.wordpress_post_url ? 'cursor-pointer' : ''}
>
<span
onClick={() => {
if (wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
>
{statusInfo.label}
</span>
</Badge>
);
};
return (
<Box display="flex" alignItems="center" gap={1}>
<div className="flex items-center gap-2">
{renderButton()}
{renderStatusChip()}
{renderStatusBadge()}
{error && (
<Alert severity="error" sx={{ mt: 1 }}>
{error}
<IconButton
size="small"
onClick={() => setError(null)}
sx={{ ml: 1 }}
>
×
</IconButton>
</Alert>
<div className="mt-2">
<Alert
variant="error"
title="Publishing Error"
message={error}
/>
</div>
)}
{/* Publish Confirmation Dialog */}
<Dialog
open={publishDialogOpen}
{/* Publish Confirmation Modal */}
<Modal
isOpen={publishDialogOpen}
onClose={() => setPublishDialogOpen(false)}
maxWidth="sm"
fullWidth
className="max-w-md p-6"
>
<DialogTitle>Publish to Site</DialogTitle>
<DialogContent>
<Typography variant="body1" gutterBottom>
Are you sure you want to publish "<strong>{contentTitle}</strong>" to your site?
</Typography>
<div className="space-y-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Publish to Site
</h2>
<Typography variant="body2" color="textSecondary" sx={{ mt: 2 }}>
<p className="text-gray-700 dark:text-gray-300">
Are you sure you want to publish "<strong>{contentTitle}</strong>" to your site?
</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
This will create a new post on your connected site with all content,
images, categories, and SEO metadata.
</Typography>
</p>
{imageGenerationStatus === 'complete' && (
<Alert severity="success" sx={{ mt: 2 }}>
Images are generated and ready for publishing
</Alert>
<Alert
variant="success"
title="Ready to Publish"
message="Images are generated and ready for publishing"
/>
)}
{imageGenerationStatus !== 'complete' && showOnlyIfImagesReady && (
<Alert severity="warning" sx={{ mt: 2 }}>
Images are still being generated. Please wait before publishing.
</Alert>
<Alert
variant="warning"
title="Images Not Ready"
message="Images are still being generated. Please wait before publishing."
/>
)}
{wpStatus?.wordpress_sync_status === 'success' && (
<Alert severity="info" sx={{ mt: 2 }}>
This content is already published to your site.
You can force republish to update the existing post.
</Alert>
<Alert
variant="info"
title="Already Published"
message="This content is already published to your site. You can force republish to update the existing post."
/>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setPublishDialogOpen(false)}>
Cancel
</Button>
{wpStatus?.wordpress_sync_status === 'success' && (
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<Button
onClick={() => handlePublishToWordPress(true)}
color="warning"
disabled={loading}
variant="outline"
tone="neutral"
onClick={() => setPublishDialogOpen(false)}
>
Force Republish
Cancel
</Button>
)}
<Button
onClick={() => handlePublishToWordPress(false)}
color="primary"
variant="contained"
disabled={loading}
>
{loading ? 'Publishing...' : 'Publish'}
</Button>
</DialogActions>
</Dialog>
</Box>
{wpStatus?.wordpress_sync_status === 'success' && (
<Button
variant="outline"
tone="warning"
onClick={() => handlePublishToWordPress(true)}
disabled={loading}
>
Force Republish
</Button>
)}
<Button
variant="primary"
tone="brand"
onClick={() => handlePublishToWordPress(false)}
disabled={loading}
startIcon={loading ? <Spinner size="sm" /> : <PaperPlaneIcon className="h-4 w-4" />}
>
{loading ? 'Publishing...' : 'Publish'}
</Button>
</div>
</div>
</Modal>
</div>
);
};