"use client"; import { useState, useRef } from "react"; import { useRouter } from "next/navigation"; import { Upload, ImageIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; interface AppFormProps { mode: "create" | "edit"; initialData?: { name: string; slug: string; description: string; appUrl: string; primaryColor: string; accentColor: string; darkBg: string; brandIdentity: string; productInfo: string; platformGuidelines: string; }; } export function AppForm({ mode, initialData }: AppFormProps) { const router = useRouter(); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [name, setName] = useState(initialData?.name ?? ""); const [slug, setSlug] = useState(initialData?.slug ?? ""); const [description, setDescription] = useState(initialData?.description ?? ""); const [appUrl, setAppUrl] = useState(initialData?.appUrl ?? ""); const [primaryColor, setPrimaryColor] = useState(initialData?.primaryColor ?? "#0079FF"); const [accentColor, setAccentColor] = useState(initialData?.accentColor ?? "#FF9400"); const [darkBg, setDarkBg] = useState(initialData?.darkBg ?? "#1a1a2e"); const [brandIdentity, setBrandIdentity] = useState(initialData?.brandIdentity ?? ""); const [productInfo, setProductInfo] = useState(initialData?.productInfo ?? ""); const [platformGuidelines, setPlatformGuidelines] = useState(initialData?.platformGuidelines ?? ""); // Icon upload state const iconInputRef = useRef(null); const [iconUploading, setIconUploading] = useState(false); const [iconVersion, setIconVersion] = useState(0); // bust cache after upload const [iconError, setIconError] = useState(""); const canUpload = mode === "edit" && !!initialData?.slug; const iconSrc = canUpload ? `/api/files/apps/${initialData!.slug}/icon.png?v=${iconVersion}` : null; async function handleIconUpload(file: File) { if (!canUpload) return; setIconUploading(true); setIconError(""); const formData = new FormData(); formData.append("file", file); formData.append("type", "icon"); const res = await fetch(`/api/apps/${initialData!.slug}/assets`, { method: "POST", body: formData, }); if (!res.ok) { const data = await res.json(); setIconError(data.error || "Upload failed"); } else { setIconVersion((v) => v + 1); } setIconUploading(false); } function autoSlug(value: string) { return value .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(""); const body = { name, slug, description, appUrl: appUrl || null, primaryColor, accentColor, darkBg, brandIdentity: brandIdentity || null, productInfo: productInfo || null, platformGuidelines: platformGuidelines || null, }; const url = mode === "create" ? "/api/apps" : `/api/apps/${initialData?.slug}`; const method = mode === "create" ? "POST" : "PATCH"; const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json(); setError(data.error || "Something went wrong"); setLoading(false); return; } router.push("/apps"); router.refresh(); } return (
{mode === "create" ? "Create App" : "Edit App"} {mode === "create" ? "Add a new app to the marketing pipeline" : "Update app details and branding"}
{ setName(e.target.value); if (mode === "create") setSlug(autoSlug(e.target.value)); }} placeholder="My App" required />
setSlug(e.target.value)} placeholder="my-app" pattern="^[a-z0-9-]+$" required disabled={mode === "edit"} />
setDescription(e.target.value)} placeholder="Brief description of the app" />
setAppUrl(e.target.value)} placeholder="https://apps.apple.com/app/..." />

App Store or website URL. Used for QR codes in ad creatives.

setPrimaryColor(e.target.value)} className="h-9 w-12 cursor-pointer rounded border" /> setPrimaryColor(e.target.value)} className="flex-1" />
setAccentColor(e.target.value)} className="h-9 w-12 cursor-pointer rounded border" /> setAccentColor(e.target.value)} className="flex-1" />
setDarkBg(e.target.value)} className="h-9 w-12 cursor-pointer rounded border" /> setDarkBg(e.target.value)} className="flex-1" />
App Icon {canUpload ? "Square icon PNG used in every ad creative." : "Save the app first, then upload the icon from the edit page."}
{/* Icon preview */} {iconSrc && ( {`${initialData?.name} { (e.target as HTMLImageElement).style.display = "none"; }} /> )} {!iconSrc && (
)} {/* Upload button */}
{ const file = e.target.files?.[0]; if (file) handleIconUpload(file); }} />

Recommended: 1024x1024 PNG

{iconError &&

{iconError}

}
Knowledge Files Markdown content that agents read before generating content. These replace the pipeline/knowledge/ files for this app.