Files
igny8/frontend/src/components/publishing/PublishingRules.tsx
2026-01-02 00:27:27 +00:00

245 lines
8.2 KiB
TypeScript

/**
* Publishing Rules Component
* Phase 6: Site Integration & Multi-Destination Publishing
*/
import React, { useState } from 'react';
import { PlusIcon, TrashIcon, ArrowUpIcon, ArrowDownIcon } from '../../icons';
import Button from '../ui/button/Button';
import Checkbox from '../form/input/Checkbox';
import Label from '../form/Label';
import SelectDropdown from '../form/SelectDropdown';
export interface PublishingRule {
id: string;
content_type: string;
trigger: 'auto' | 'manual' | 'scheduled';
destinations: string[];
priority: number;
enabled: boolean;
schedule?: string; // Cron expression for scheduled
}
interface PublishingRulesProps {
rules: PublishingRule[];
onChange: (rules: PublishingRule[]) => void;
}
const CONTENT_TYPES = [
{ value: 'blog_post', label: 'Blog Post' },
{ value: 'page', label: 'Page' },
{ value: 'product', label: 'Product' },
{ value: 'all', label: 'All Content' },
];
const TRIGGERS = [
{ value: 'auto', label: 'Auto-Publish' },
{ value: 'manual', label: 'Manual' },
{ value: 'scheduled', label: 'Scheduled' },
];
const DESTINATIONS = [
{ value: 'sites', label: 'IGNY8 Sites' },
{ value: 'wordpress', label: 'WordPress' },
{ value: 'shopify', label: 'Shopify' },
];
export default function PublishingRules({ rules, onChange }: PublishingRulesProps) {
const [localRules, setLocalRules] = useState<PublishingRule[]>(rules);
const handleAddRule = () => {
const newRule: PublishingRule = {
id: `rule_${Date.now()}`,
content_type: 'all',
trigger: 'manual',
destinations: ['sites'],
priority: localRules.length + 1,
enabled: true,
};
const updated = [...localRules, newRule];
setLocalRules(updated);
onChange(updated);
};
const handleDeleteRule = (id: string) => {
const updated = localRules.filter((r) => r.id !== id);
setLocalRules(updated);
onChange(updated);
};
const handleUpdateRule = (id: string, field: keyof PublishingRule, value: any) => {
const updated = localRules.map((rule) =>
rule.id === id ? { ...rule, [field]: value } : rule
);
setLocalRules(updated);
onChange(updated);
};
const handleMoveRule = (id: string, direction: 'up' | 'down') => {
const index = localRules.findIndex((r) => r.id === id);
if (index === -1) return;
const newIndex = direction === 'up' ? index - 1 : index + 1;
if (newIndex < 0 || newIndex >= localRules.length) return;
const updated = [...localRules];
[updated[index], updated[newIndex]] = [updated[newIndex], updated[index]];
// Update priorities
updated.forEach((rule, i) => {
rule.priority = i + 1;
});
setLocalRules(updated);
onChange(updated);
};
const handleToggleDestinations = (ruleId: string, destination: string) => {
const rule = localRules.find((r) => r.id === ruleId);
if (!rule) return;
const destinations = rule.destinations.includes(destination)
? rule.destinations.filter((d) => d !== destination)
: [...rule.destinations, destination];
handleUpdateRule(ruleId, 'destinations', destinations);
};
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Advanced Publishing Rules
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
Set specific rules for different types of content
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Example: Publish blog posts to WordPress but guides to your main site
</p>
</div>
<Button onClick={handleAddRule} variant="primary" size="sm" startIcon={<PlusIcon className="w-4 h-4" />}>
Add a Publishing Rule
</Button>
</div>
{localRules.length === 0 ? (
<div className="text-center py-8 border border-dashed border-gray-300 dark:border-gray-700 rounded-lg">
<p className="text-gray-600 dark:text-gray-400 mb-4">No publishing rules configured</p>
<Button onClick={handleAddRule} variant="outline">
Add Your First Rule
</Button>
</div>
) : (
<div className="space-y-3">
{localRules.map((rule, index) => (
<div
key={rule.id}
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 space-y-3"
>
<div className="flex items-start justify-between">
<div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label>Content Type</Label>
<SelectDropdown
options={CONTENT_TYPES}
value={rule.content_type}
onChange={(value) =>
handleUpdateRule(rule.id, 'content_type', value)
}
/>
</div>
<div>
<Label>Trigger</Label>
<SelectDropdown
options={TRIGGERS}
value={rule.trigger}
onChange={(value) =>
handleUpdateRule(rule.id, 'trigger', value)
}
/>
</div>
<div>
<Label>Priority</Label>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => handleMoveRule(rule.id, 'up')}
disabled={index === 0}
>
<ArrowUpIcon className="w-4 h-4" />
</Button>
<span className="text-sm font-medium">{rule.priority}</span>
<Button
variant="ghost"
size="sm"
onClick={() => handleMoveRule(rule.id, 'down')}
disabled={index === localRules.length - 1}
>
<ArrowDownIcon className="w-4 h-4" />
</Button>
</div>
</div>
</div>
<div className="flex items-center gap-2 ml-4">
<Checkbox
checked={rule.enabled}
onChange={(e) =>
handleUpdateRule(rule.id, 'enabled', e.target.checked)
}
label="Enabled"
/>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteRule(rule.id)}
>
<TrashIcon className="w-4 h-4" />
</Button>
</div>
</div>
<div>
<Label>Destinations</Label>
<div className="flex flex-wrap gap-3 mt-2">
{DESTINATIONS.map((dest) => (
<Checkbox
key={dest.value}
checked={rule.destinations.includes(dest.value)}
onChange={() => handleToggleDestinations(rule.id, dest.value)}
label={dest.label}
/>
))}
</div>
</div>
{rule.trigger === 'scheduled' && (
<div>
<Label>Schedule (Cron Expression)</Label>
<input
type="text"
value={rule.schedule || ''}
onChange={(e) =>
handleUpdateRule(rule.id, 'schedule', e.target.value)
}
placeholder="0 0 * * *"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Cron format: minute hour day month weekday
</p>
</div>
)}
</div>
))}
</div>
)}
</div>
);
}