rsocials-online/src/components/campaigns/details/PostForm.tsx

163 lines
5.7 KiB
TypeScript

"use client";
import { Plus, X, CheckCircle2, XCircle, Clock, Zap } from "lucide-react";
import { useCampaignStore } from "@/lib/stores/campaign-store";
import type { PostData, Platform, PostPlatformContent, PostSyncState, PostMetrics } from "@/lib/types/campaign";
import { PLATFORMS } from "@/lib/types/campaign";
interface Props {
nodeId: string;
data: PostData;
}
function SyncBadge({ state }: { state: PostSyncState }) {
const icons: Record<string, React.ReactNode> = {
synced: <CheckCircle2 className="w-3 h-3 text-emerald-500" />,
failed: <XCircle className="w-3 h-3 text-red-500" />,
pending: <Clock className="w-3 h-3 text-amber-500" />,
published: <Zap className="w-3 h-3 text-blue-500" />,
};
return (
<div className="flex items-center gap-1 text-xs">
{icons[state.status]}
<span
className={
state.status === "synced"
? "text-emerald-500"
: state.status === "failed"
? "text-red-500"
: state.status === "published"
? "text-blue-500"
: "text-amber-500"
}
>
{state.status}
</span>
{state.error && (
<span className="text-red-400 truncate max-w-[150px]" title={state.error}>
{state.error}
</span>
)}
</div>
);
}
function MetricsBadge({ metrics }: { metrics: PostMetrics }) {
const parts: string[] = [];
if (metrics.likes != null) parts.push(`${metrics.likes} likes`);
if (metrics.comments != null) parts.push(`${metrics.comments} comments`);
if (metrics.shares != null) parts.push(`${metrics.shares} shares`);
if (metrics.impressions != null) parts.push(`${metrics.impressions} impr.`);
if (parts.length === 0) return null;
return (
<div className="text-xs text-muted-foreground bg-muted/50 rounded px-2 py-0.5 mt-1">
{parts.join(" · ")}
{metrics.fetchedAt && (
<span className="ml-1 opacity-50">
({new Date(metrics.fetchedAt).toLocaleTimeString()})
</span>
)}
</div>
);
}
export function PostForm({ nodeId, data }: Props) {
const updateNodeData = useCampaignStore((s) => s.updateNodeData);
const addPlatform = (platformId: Platform) => {
if (data.platforms.some((p) => p.platform === platformId)) return;
const newPlatforms: PostPlatformContent[] = [
...data.platforms,
{ platform: platformId, content: data.sharedContent },
];
updateNodeData(nodeId, { platforms: newPlatforms });
};
const removePlatform = (platformId: Platform) => {
updateNodeData(nodeId, {
platforms: data.platforms.filter((p) => p.platform !== platformId),
});
};
const updatePlatformContent = (platformId: Platform, content: string) => {
updateNodeData(nodeId, {
platforms: data.platforms.map((p) =>
p.platform === platformId ? { ...p, content } : p
),
});
};
const availablePlatforms = PLATFORMS.filter(
(p) => !data.platforms.some((dp) => dp.platform === p.id)
);
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-foreground mb-1.5">Shared Content</label>
<textarea
value={data.sharedContent}
onChange={(e) => updateNodeData(nodeId, { sharedContent: e.target.value })}
placeholder="Write your post content..."
rows={3}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground resize-none focus:outline-none focus:ring-2 focus:ring-primary/50"
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-1.5">Platforms</label>
{data.platforms.map((pp) => {
const info = PLATFORMS.find((p) => p.id === pp.platform);
const syncState = data.postizSync?.[pp.platform];
const platformMetrics = data.metrics?.[pp.platform];
return (
<div key={pp.platform} className="mb-3 rounded-lg border border-border p-3">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium" style={{ color: info?.color }}>
{info?.label}
</span>
<div className="flex items-center gap-2">
{syncState && <SyncBadge state={syncState} />}
<button
onClick={() => removePlatform(pp.platform)}
className="text-muted-foreground hover:text-destructive"
>
<X className="w-4 h-4" />
</button>
</div>
</div>
<textarea
value={pp.content}
onChange={(e) => updatePlatformContent(pp.platform, e.target.value)}
placeholder={`Custom content for ${info?.label}...`}
rows={2}
className="w-full rounded-md border border-border bg-background px-2 py-1.5 text-xs text-foreground resize-none focus:outline-none focus:ring-1 focus:ring-primary/50"
/>
{platformMetrics && <MetricsBadge metrics={platformMetrics} />}
</div>
);
})}
{availablePlatforms.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-2">
{availablePlatforms.map((p) => (
<button
key={p.id}
onClick={() => addPlatform(p.id)}
className="inline-flex items-center gap-1 rounded-full border border-border px-2.5 py-1 text-xs text-muted-foreground hover:border-primary hover:text-primary transition-colors"
>
<Plus className="w-3 h-3" />
{p.label}
</button>
))}
</div>
)}
</div>
</div>
);
}