feat: complete marketing command center with pipeline, UI, and asset generation
- Dashboard with campaign management, asset gallery, and publishing queue - 7-agent pipeline: trend scout, research, scripts, ad creative, video, copy, distribution - Campaign form with screenshot upload, goal picker, platform selection - Campaign detail view with Details/Pipeline/Assets/Chat tabs - Two-set image generation: Gemini AI (NanoBanana MCP) + Canvas Design posters - Remotion video rendering with phone.png frame and real screenshot alignment - honeyDue branding: blue #0079FF, orange #FF9400, Inter font, warm off-white - Asset cards with source badges (Gemini/Canvas/Remotion/Playwright) - Markdown/JSON render endpoint for viewing pipeline outputs as HTML - Settings page with Tavily, Gemini, Postiz, Nextdoor integration management - Claude Chat for campaign feedback loop with streaming SSE - Postiz publishing modal with scheduling - Auth with NextAuth credentials + JWT sessions - SQLite via Prisma with better-sqlite3 adapter Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
interface PostizPushModalProps {
|
||||
assetIds: string[];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function PostizPushModal({ assetIds, onClose }: PostizPushModalProps) {
|
||||
const [scheduledAt, setScheduledAt] = useState(() => {
|
||||
const d = new Date();
|
||||
d.setHours(d.getHours() + 1);
|
||||
return d.toISOString().slice(0, 16);
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState<string | null>(null);
|
||||
|
||||
async function handlePush() {
|
||||
setLoading(true);
|
||||
|
||||
const res = await fetch("/api/postiz", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
assetIds,
|
||||
scheduledAt: new Date(scheduledAt).toISOString(),
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
const scheduled = data.results?.filter(
|
||||
(r: { status: string }) => r.status === "scheduled"
|
||||
).length;
|
||||
setResult(`${scheduled} asset(s) scheduled successfully.`);
|
||||
setTimeout(onClose, 2000);
|
||||
} else {
|
||||
setResult(`Error: ${data.error || "Failed to push"}`);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={onClose}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Push to Postiz</DialogTitle>
|
||||
<DialogDescription>
|
||||
Schedule {assetIds.length} asset(s) for publishing via Postiz.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="schedule">Schedule Date & Time</Label>
|
||||
<Input
|
||||
id="schedule"
|
||||
type="datetime-local"
|
||||
value={scheduledAt}
|
||||
onChange={(e) => setScheduledAt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{result && (
|
||||
<p
|
||||
className={`text-sm ${
|
||||
result.startsWith("Error") ? "text-red-500" : "text-green-600"
|
||||
}`}
|
||||
>
|
||||
{result}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handlePush} disabled={loading}>
|
||||
{loading ? "Scheduling..." : "Schedule"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user