From 1b69eaf219a730497e314c89764384b72e04639d Mon Sep 17 00:00:00 2001 From: Harun CAN Date: Tue, 28 Apr 2026 09:48:43 +0200 Subject: [PATCH] feat: Implement text-to-video and fix hydration UI issues --- .../[locale]/(dashboard)/dashboard/page.tsx | 15 ++ .../dashboard/projects/[id]/page.tsx | 2 +- .../dashboard/text-to-video/error.tsx | 22 ++ .../dashboard/text-to-video/page.tsx | 252 ++++++++++++++++++ src/components/dashboard/dashboard-charts.tsx | 9 +- src/components/layout/app-shell.tsx | 1 + src/components/project/render-progress.tsx | 33 ++- src/components/ui/provider.tsx | 28 +- src/hooks/use-api.ts | 13 + src/lib/api/api-service.ts | 13 + src/theme/theme.ts | 1 + test-puppeteer.js | 13 + tsconfig.tsbuildinfo | 2 +- 13 files changed, 387 insertions(+), 17 deletions(-) create mode 100644 src/app/[locale]/(dashboard)/dashboard/text-to-video/error.tsx create mode 100644 src/app/[locale]/(dashboard)/dashboard/text-to-video/page.tsx create mode 100644 test-puppeteer.js diff --git a/src/app/[locale]/(dashboard)/dashboard/page.tsx b/src/app/[locale]/(dashboard)/dashboard/page.tsx index 57fa9f3..76ebf3b 100644 --- a/src/app/[locale]/(dashboard)/dashboard/page.tsx +++ b/src/app/[locale]/(dashboard)/dashboard/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { FolderOpen, @@ -95,6 +96,12 @@ function getStatCards(data?: typeof MOCK_STATS, creditBalance?: { balance: numbe } export default function DashboardPage() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + // Real API hook'ları — mock modunda çağrılmaz const statsQuery = useDashboardStats(); const creditQuery = useCreditBalance(); @@ -106,6 +113,14 @@ export default function DashboardPage() { const statCards = getStatCards(statsData, creditData); + if (!mounted) { + return ( +
+ +
+ ); + } + return ( - + )} diff --git a/src/app/[locale]/(dashboard)/dashboard/text-to-video/error.tsx b/src/app/[locale]/(dashboard)/dashboard/text-to-video/error.tsx new file mode 100644 index 0000000..eadeece --- /dev/null +++ b/src/app/[locale]/(dashboard)/dashboard/text-to-video/error.tsx @@ -0,0 +1,22 @@ +'use client' +import { useEffect } from 'react' + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + useEffect(() => { + console.error('TEXT TO VIDEO ERROR:', error) + }, [error]) + + return ( +
+

Something went wrong!

+
{error.message}
+
{error.stack}
+
+ ) +} diff --git a/src/app/[locale]/(dashboard)/dashboard/text-to-video/page.tsx b/src/app/[locale]/(dashboard)/dashboard/text-to-video/page.tsx new file mode 100644 index 0000000..94b614d --- /dev/null +++ b/src/app/[locale]/(dashboard)/dashboard/text-to-video/page.tsx @@ -0,0 +1,252 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { useCreateFromText } from "@/hooks/use-api"; +import { useToast } from "@/components/ui/toast"; +import { + FileText, + Loader2, + ArrowRight, + Clock, + Palette, + Monitor, + Smartphone, + Square, + Wand2, + Type, +} from "lucide-react"; + +const videoStyles = [ + { id: "CINEMATIC", label: "Sinematik", emoji: "🎬" }, + { id: "DOCUMENTARY", label: "Belgesel", emoji: "📹" }, + { id: "EDUCATIONAL", label: "Eğitim", emoji: "📚" }, + { id: "STORYTELLING", label: "Hikâye", emoji: "📖" }, + { id: "NEWS", label: "Haber", emoji: "📰" }, +]; + +const aspectRatios = [ + { id: "PORTRAIT_9_16", label: "9:16", icon: Smartphone, desc: "Shorts / Reels" }, + { id: "SQUARE_1_1", label: "1:1", icon: Square, desc: "Instagram" }, + { id: "LANDSCAPE_16_9", label: "16:9", icon: Monitor, desc: "YouTube" }, +]; + +const languages = [ + { code: "tr", label: "Türkçe", flag: "🇹🇷" }, + { code: "en", label: "English", flag: "🇺🇸" }, + { code: "de", label: "Deutsch", flag: "🇩🇪" }, + { code: "es", label: "Español", flag: "🇪🇸" }, +]; + +export default function TextToVideoPage() { + const router = useRouter(); + const { toast } = useToast(); + + const createFromText = useCreateFromText(); + + const [textInput, setTextInput] = useState(""); + + const [style, setStyle] = useState("CINEMATIC"); + const [cinematicReference, setCinematicReference] = useState(""); + const [duration, setDuration] = useState(60); + const [aspectRatio, setAspectRatio] = useState("PORTRAIT_9_16"); + const [language, setLanguage] = useState("tr"); + + const handleGenerate = async () => { + if (!textInput.trim()) { + toast("error", "Lütfen bir konu, hikaye veya metin girin."); + return; + } + + try { + const result: any = await createFromText.mutateAsync({ + text: textInput, + language, + aspectRatio, + videoStyle: style, + cinematicReference: cinematicReference ? cinematicReference : undefined, + targetDuration: duration, + }); + + toast("success", "Video projesi başarıyla oluşturuldu!"); + router.push(`/dashboard/projects/${result.id}`); + } catch (error) { + toast("error", "Proje oluşturulurken bir hata oluştu."); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+

+ Metinden Video Üret +

+

+ İstediğiniz konuyu, bir hikayeyi veya makaleyi kopyalayıp yapıştırın; yapay zeka sizin için detaylı bir video senaryosu üretsin. +

+
+ + {/* Input */} +
+
+ +