"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { ArrowLeft, ArrowRight, Check, Download, Loader2, Mic, MicOff, RefreshCw, Copy, CheckCircle, } from "lucide-react"; // Helper to get correct path based on subdomain function useZinePath() { const [isSubdomain, setIsSubdomain] = useState(false); useEffect(() => { setIsSubdomain(window.location.hostname.startsWith("zine.")); }, []); return (path: string) => { if (isSubdomain) { return path.replace(/^\/zine/, "") || "/"; } return path; }; } interface PageOutline { pageNumber: number; type: string; title: string; keyPoints: string[]; imagePrompt: string; } interface ZineState { id: string; topic: string; style: string; tone: string; outline: PageOutline[]; pages: string[]; currentStep: "outline" | "generate" | "refine" | "download"; generatingPage: number | null; printLayoutUrl: string | null; } const STEPS = ["outline", "generate", "refine", "download"] as const; const STEP_LABELS = { outline: "Review Outline", generate: "Generate Pages", refine: "Refine Pages", download: "Download & Share", }; export default function CreatePage() { const router = useRouter(); const getPath = useZinePath(); const [state, setState] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [feedback, setFeedback] = useState(""); const [isListening, setIsListening] = useState(false); const [copied, setCopied] = useState(false); // Initialize from session storage useEffect(() => { const input = sessionStorage.getItem("zineInput"); if (!input) { router.push(getPath("/zine")); return; } const { topic, style, tone } = JSON.parse(input); generateOutline(topic, style, tone); }, [router]); const generateOutline = async (topic: string, style: string, tone: string) => { setLoading(true); setError(null); try { const response = await fetch("/api/zine/outline", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ topic, style, tone }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Failed to generate outline"); } const data = await response.json(); setState({ id: data.id, topic, style, tone, outline: data.outline, pages: new Array(8).fill(""), currentStep: "outline", generatingPage: null, printLayoutUrl: null, }); } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong"); } finally { setLoading(false); } }; const generatePages = async () => { if (!state) return; setState((s) => (s ? { ...s, currentStep: "generate" } : s)); for (let i = 1; i <= 8; i++) { if (state.pages[i - 1]) continue; // Skip already generated pages setState((s) => (s ? { ...s, generatingPage: i } : s)); try { const response = await fetch("/api/zine/generate-page", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ zineId: state.id, pageNumber: i, outline: state.outline[i - 1], style: state.style, tone: state.tone, }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || `Failed to generate page ${i}`); } const data = await response.json(); // Add cache-busting timestamp to force image reload const imageUrlWithTimestamp = `${data.imageUrl}&t=${Date.now()}`; setState((s) => { if (!s) return s; const newPages = [...s.pages]; newPages[i - 1] = imageUrlWithTimestamp; return { ...s, pages: newPages }; }); } catch (err) { console.error(`Error generating page ${i}:`, err); setError(`Failed to generate page ${i}`); return; } } setState((s) => (s ? { ...s, generatingPage: null, currentStep: "refine" } : s)); }; const regeneratePage = async () => { if (!state || !feedback.trim()) return; setState((s) => (s ? { ...s, generatingPage: currentPage } : s)); try { const response = await fetch("/api/zine/regenerate-page", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ zineId: state.id, pageNumber: currentPage, currentOutline: state.outline[currentPage - 1], feedback: feedback.trim(), style: state.style, tone: state.tone, }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Failed to regenerate page"); } const data = await response.json(); setState((s) => { if (!s) return s; const newPages = [...s.pages]; newPages[currentPage - 1] = data.imageUrl; const newOutline = [...s.outline]; newOutline[currentPage - 1] = data.updatedOutline; return { ...s, pages: newPages, outline: newOutline, generatingPage: null }; }); setFeedback(""); } catch (err) { setError(err instanceof Error ? err.message : "Failed to regenerate"); setState((s) => (s ? { ...s, generatingPage: null } : s)); } }; const createPrintLayout = async () => { if (!state) return; setLoading(true); try { const response = await fetch("/api/zine/print-layout", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ zineId: state.id, zineName: state.topic.slice(0, 20).replace(/[^a-zA-Z0-9]/g, "_"), }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Failed to create print layout"); } const data = await response.json(); setState((s) => s ? { ...s, printLayoutUrl: data.printLayoutUrl, currentStep: "download" } : s ); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create print layout"); } finally { setLoading(false); } }; const handleVoiceInput = useCallback(() => { if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) { alert("Voice input not supported"); return; } const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; const recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.onstart = () => setIsListening(true); recognition.onend = () => setIsListening(false); recognition.onerror = () => setIsListening(false); recognition.onresult = (event: any) => { const transcript = event.results[0][0].transcript; setFeedback((prev) => (prev ? prev + " " + transcript : transcript)); }; if (isListening) { recognition.stop(); } else { recognition.start(); } }, [isListening]); const copyShareLink = async () => { if (!state) return; const shareUrl = `${window.location.origin}${getPath(`/zine/z/${state.id}`)}`; await navigator.clipboard.writeText(shareUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); }; if (loading && !state) { return (

Generating your outline...

); } if (error && !state) { return (

Error

{error}

); } if (!state) return null; return (
{/* Header with Progress */}

{state.topic}

{/* Progress Steps */}
{STEPS.map((step, i) => (
= i ? "bg-black text-white" : "bg-gray-200 text-gray-500" }`} > {STEPS.indexOf(state.currentStep) > i ? ( ) : ( i + 1 )}
{i < STEPS.length - 1 && (
i ? "bg-black" : "bg-gray-200" }`} /> )}
))}
{STEPS.map((step) => ( {STEP_LABELS[step]} ))}
{/* Error Banner */} {error && (
{error}
)} {/* Step Content */}
{/* Step 1: Outline Review */} {state.currentStep === "outline" && (

Your 8-Page Outline

{state.outline.map((page, i) => (
Page {page.pageNumber} • {page.type}

{page.title}

    {page.keyPoints.map((point, j) => (
  • • {point}
  • ))}
))}
)} {/* Step 2: Page Generation */} {state.currentStep === "generate" && (

Generating Your Zine

Page {state.generatingPage || state.pages.filter((p) => p).length} of 8

{/* Overall Progress Bar */}
Progress {Math.round((state.pages.filter((p) => p).length / 8) * 100)}%
p).length / 8) * 100}%` }} />
{[1, 2, 3, 4, 5, 6, 7, 8].map((num) => (
{state.pages[num - 1] ? : num}
))}
{/* Current Page Being Generated */} {state.generatingPage && (

{state.outline[state.generatingPage - 1]?.title}

{state.outline[state.generatingPage - 1]?.type} • Page {state.generatingPage}

)} {/* Thumbnail Grid */}
{state.outline.map((page, i) => (
{state.pages[i] ? ( {`Page console.log(`Page ${i + 1} image loaded`)} /> ) : state.generatingPage === i + 1 ? (
Generating...
) : (
P{i + 1}

Pending

)}
))}
)} {/* Step 3: Page Refinement */} {state.currentStep === "refine" && (

Page {currentPage} of 8

{/* Current Page Preview */}
{state.generatingPage === currentPage ? (
) : ( {`Page )}

{state.outline[currentPage - 1].title}

{state.outline[currentPage - 1].keyPoints.join(" • ")}