diff --git a/app/(dashboard)/dashboard/config/config-form.tsx b/app/(dashboard)/dashboard/config/config-form.tsx new file mode 100644 index 00000000..01e50f02 --- /dev/null +++ b/app/(dashboard)/dashboard/config/config-form.tsx @@ -0,0 +1,597 @@ +"use client"; + +import { useState } from "react"; +import { toast } from "sonner"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; +import { Loader2, Save, Plus, Trash2, X } from "lucide-react"; +import type { EngineConfig } from "@/lib/types/engine-config"; + +const ALL_DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + +interface ConfigFormProps { + initialConfig: EngineConfig; +} + +export function ConfigForm({ initialConfig }: ConfigFormProps) { + const [config, setConfig] = useState(initialConfig); + const [saving, setSaving] = useState(false); + const [newCategory, setNewCategory] = useState(""); + + const update = (key: K, value: EngineConfig[K]) => { + setConfig((prev) => ({ ...prev, [key]: value })); + }; + + const handleSave = async () => { + setSaving(true); + try { + const res = await fetch("/api/dashboard/config", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(config), + }); + if (res.ok) { + toast.success("Config saved. Changes propagate within 5 minutes."); + } else { + const data = await res.json(); + toast.error(data.error || "Failed to save config"); + } + } catch { + toast.error("Failed to save config"); + } finally { + setSaving(false); + } + }; + + const toggleDay = (day: string) => { + const days = config.publishDays || []; + update( + "publishDays", + days.includes(day) ? days.filter((d) => d !== day) : [...days, day] + ); + }; + + const addCategory = () => { + const trimmed = newCategory.trim(); + if (!trimmed || (config.contentCategories || []).includes(trimmed)) return; + update("contentCategories", [...(config.contentCategories || []), trimmed]); + setNewCategory(""); + }; + + const removeCategory = (cat: string) => { + update( + "contentCategories", + (config.contentCategories || []).filter((c) => c !== cat) + ); + }; + + const updateTier = ( + index: number, + field: "name" | "description" | "price", + value: string | number + ) => { + const tiers = [...(config.rateCardTiers || [])]; + tiers[index] = { ...tiers[index], [field]: value }; + update("rateCardTiers", tiers); + }; + + const addTier = () => { + update("rateCardTiers", [ + ...(config.rateCardTiers || []), + { name: "", description: "", price: 0 }, + ]); + }; + + const removeTier = (index: number) => { + update( + "rateCardTiers", + (config.rateCardTiers || []).filter((_, i) => i !== index) + ); + }; + + return ( +
+ {/* Pipeline Control */} + + + Pipeline Control + + Control auto-publishing, quality gates, and pipeline limits. + + + +
+ + +
+
+ + + update("qualityThreshold", Number(e.target.value)) + } + className="w-full accent-primary" + /> +
+
+
+ + + update("reviewTimeoutDays", Number(e.target.value)) + } + /> +
+
+ + + update("maxIdeasPerRun", Number(e.target.value)) + } + /> +
+
+
+
+ + {/* Content Cadence */} + + + Content Cadence + + How much content to produce and when to publish. + + + +
+
+ + + update("longFormPerWeek", Number(e.target.value)) + } + /> +
+
+ + + update("shortsPerDay", Number(e.target.value)) + } + /> +
+
+ + + update("blogsPerWeek", Number(e.target.value)) + } + /> +
+
+ +
+ +
+ {ALL_DAYS.map((day) => ( + toggleDay(day)} + > + {day} + + ))} +
+
+ +
+ +
+ {(config.contentCategories || []).map((cat) => ( + + {cat} + + + ))} +
+
+ setNewCategory(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + addCategory(); + } + }} + className="flex-1" + /> + +
+
+
+
+ + {/* AI & Generation */} + + + AI & Generation + + Model selection and system instructions for content generation. + + + +
+
+ + update("geminiModel", e.target.value)} + placeholder="gemini-2.0-flash" + /> +
+
+ + update("infographicModel", e.target.value)} + placeholder="imagen-3.0-generate-002" + /> +
+
+
+ +