66c2bbec8b
- 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>
101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
"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>
|
|
);
|
|
}
|