"use client"; import { useState, useRef, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Upload, X, Check, ArrowRight, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import { useS3Upload } from "next-s3-upload"; import { useAuth, SignInButton } from "@clerk/nextjs"; import { COMIC_STYLES } from "@/lib/constants"; import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut"; interface ComicCreationFormProps { prompt: string; setPrompt: (prompt: string) => void; style: string; setStyle: (style: string) => void; characterFiles: File[]; setCharacterFiles: (files: File[]) => void; isLoading: boolean; setIsLoading: (loading: boolean) => void; } export function ComicCreationForm({ prompt, setPrompt, style, setStyle, characterFiles, setCharacterFiles, isLoading, setIsLoading, }: ComicCreationFormProps) { const router = useRouter(); const [loadingStep, setLoadingStep] = useState(0); const { toast } = useToast(); const { uploadToS3 } = useS3Upload(); const { isSignedIn, isLoaded } = useAuth(); const [hasApiKey, setHasApiKey] = useState(false); const [previews, setPreviews] = useState([]); const [showPreview, setShowPreview] = useState(null); const [showStyleDropdown, setShowStyleDropdown] = useState(false); const fileInputRef = useRef(null); const textareaRef = useRef(null); // Check if user has their own API key set useEffect(() => { const checkApiKey = () => { const apiKey = localStorage.getItem("together_api_key"); setHasApiKey(!!apiKey); }; checkApiKey(); // Listen for storage changes window.addEventListener("storage", checkApiKey); return () => window.removeEventListener("storage", checkApiKey); }, []); useEffect(() => { if (isLoading) { setShowStyleDropdown(false); } }, [isLoading]); useEffect(() => { // Auto-focus the textarea when component mounts if (textareaRef.current) { textareaRef.current.focus(); } }, []); // Keyboard shortcut for form submission useKeyboardShortcut(() => { if (!isLoading && prompt.trim()) { handleCreate(); } }, { disabled: isLoading }); const handleFiles = (newFiles: FileList | null) => { if (!newFiles) return; const validFiles = Array.from(newFiles).filter((file) => file.type.startsWith("image/") ); const totalFiles = [...characterFiles, ...validFiles].slice(0, 2); // Max 2 files setCharacterFiles(totalFiles); // Generate previews for all files const newPreviews: string[] = []; totalFiles.forEach((file, index) => { const reader = new FileReader(); reader.onload = (e) => { newPreviews[index] = e.target?.result as string; if (newPreviews.filter(Boolean).length === totalFiles.length) { setPreviews([...newPreviews]); } }; reader.readAsDataURL(file); }); }; const removeFile = (index: number) => { const newFiles = characterFiles.filter((_, i) => i !== index); const newPreviews = previews.filter((_, i) => i !== index); setCharacterFiles(newFiles); setPreviews(newPreviews); setShowPreview(null); if (fileInputRef.current) { fileInputRef.current.value = ""; } }; useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; if (!target.closest(".dropdown-container")) { setShowStyleDropdown(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const handleCreate = async () => { if (!prompt.trim()) { toast({ title: "Prompt required", description: "Please enter a prompt to generate your comic", variant: "destructive", duration: 3000, }); return; } setIsLoading(true); setLoadingStep(0); try { const apiKey = localStorage.getItem("together_api_key"); const characterUploads = await Promise.all( characterFiles.map((file) => uploadToS3(file).then(({ url }) => url)) ); // Use API to create story and generate first page const response = await fetch("/api/generate-comic", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ prompt, apiKey, style, characterImages: characterUploads, }), }); if (!response.ok) { const errorData = await response.json(); if (response.status === 429 && errorData.isRateLimited) { throw new Error(errorData.error); } throw new Error(errorData.error || "Failed to create story"); } const result = await response.json(); // Redirect to the story editor using slug router.push(`/editor/${result.storySlug}`); } catch (error) { console.error("Error creating comic:", error); toast({ title: "Creation failed", description: error instanceof Error ? error.message : "Failed to create comic. Please try again.", variant: "destructive", duration: 4000, }); setIsLoading(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { const isEnter = e.key === "Enter" || e.key === "\n" || e.keyCode === 13; const isModifierPressed = e.shiftKey || e.ctrlKey || e.metaKey; // metaKey for Cmd on Mac if (isEnter && isModifierPressed) { e.preventDefault(); handleCreate(); } }; const loadingSteps = [ "Enhancing prompt...", "Generating scenes...", "Creating your comic...", ]; return ( <>