From 75de91848e880e0ccbe66cb4e53b83d9ddc30856 Mon Sep 17 00:00:00 2001 From: Harun CAN Date: Sun, 12 Apr 2026 11:44:20 +0200 Subject: [PATCH] main --- next-env.d.ts | 2 +- .../dashboard/document-to-video/page.tsx | 252 ++++++++++++++++++ .../dashboard/youtube-to-video/page.tsx | 249 +++++++++++++++++ src/components/layout/app-shell.tsx | 4 +- src/components/project/scene-card.tsx | 4 +- src/hooks/use-api.ts | 27 ++ src/lib/api/api-service.ts | 37 +++ 7 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 src/app/[locale]/(dashboard)/dashboard/document-to-video/page.tsx create mode 100644 src/app/[locale]/(dashboard)/dashboard/youtube-to-video/page.tsx diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/app/[locale]/(dashboard)/dashboard/document-to-video/page.tsx b/src/app/[locale]/(dashboard)/dashboard/document-to-video/page.tsx new file mode 100644 index 0000000..e061e46 --- /dev/null +++ b/src/app/[locale]/(dashboard)/dashboard/document-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 { useCreateFromDocument } from "@/hooks/use-api"; +import { useToast } from "@/components/ui/toast"; +import { + FileText, + Loader2, + ArrowRight, + Clock, + Palette, + Monitor, + Smartphone, + Square, + Wand2, +} 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 DocumentToVideoPage() { + const router = useRouter(); + const { toast } = useToast(); + + const createFromDocument = useCreateFromDocument(); + + const [file, setFile] = useState(null); + const [style, setStyle] = useState("CINEMATIC"); + const [duration, setDuration] = useState(60); + const [aspectRatio, setAspectRatio] = useState("PORTRAIT_9_16"); + const [language, setLanguage] = useState("tr"); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + setFile(e.target.files[0]); + } + }; + + const handleGenerate = async () => { + if (!file) { + toast.error("Lütfen bir belge seçin."); + return; + } + + try { + const result: any = await createFromDocument.mutateAsync({ + file, + language, + aspectRatio, + videoStyle: style, + targetDuration: duration, + }); + + toast.success("Belge → Video projesi oluşturuldu!"); + router.push(`/dashboard/projects/${result.id}`); + } catch (error) { + toast.error("Proje oluşturulurken hata oluştu."); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+

+ Belgeden Video Üret +

+

+ PDF, Word, TXT vb. belgenizi yükleyin, yapay zeka içeriği tarayıp senaryolastirsin. +

+
+ + {/* Input */} +
+
+ +
+ +
+
+
+ + {/* Video Settings */} +
+
+ +
+ {languages.map((l) => ( + + ))} +
+
+ +
+ +
+ {videoStyles.map((s) => ( + + ))} +
+
+ +
+
+ + setDuration(Number(e.target.value))} + className="w-full h-1.5 rounded-full bg-[var(--color-bg-elevated)] appearance-none cursor-pointer + [&::-webkit-slider-thumb]:appearance-none + [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 + [&::-webkit-slider-thumb]:rounded-full + [&::-webkit-slider-thumb]:bg-blue-500 + [&::-webkit-slider-thumb]:shadow-[0_0_12px_rgba(59,130,246,0.4)] + [&::-webkit-slider-thumb]:cursor-grab" + /> +
+ +
+ +
+ {aspectRatios.map((ar) => { + const Icon = ar.icon; + return ( + + ); + })} +
+
+
+ + + +

+ Bu işlem 1 kredi kullanır • AI senaryo + görsel üretim dahil +

+
+ +
+ ); +} diff --git a/src/app/[locale]/(dashboard)/dashboard/youtube-to-video/page.tsx b/src/app/[locale]/(dashboard)/dashboard/youtube-to-video/page.tsx new file mode 100644 index 0000000..ba36895 --- /dev/null +++ b/src/app/[locale]/(dashboard)/dashboard/youtube-to-video/page.tsx @@ -0,0 +1,249 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { motion, AnimatePresence } from "framer-motion"; +import { + Link2, + Loader2, + ArrowRight, + Clock, + Palette, + Monitor, + Smartphone, + Square, + Sparkles, + Wand2, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { useCreateFromYoutube } from "@/hooks/use-api"; +import { useToast } from "@/components/ui/toast"; + +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 YoutubeToVideoPage() { + const router = useRouter(); + const { toast } = useToast(); + + const createFromYoutube = useCreateFromYoutube(); + + const [youtubeUrl, setYoutubeUrl] = useState(""); + const [style, setStyle] = useState("CINEMATIC"); + const [duration, setDuration] = useState(60); + const [aspectRatio, setAspectRatio] = useState("PORTRAIT_9_16"); + const [language, setLanguage] = useState("tr"); + + const handleGenerate = async () => { + if (!youtubeUrl.includes("youtube.com") && !youtubeUrl.includes("youtu.be")) { + toast.error("Lütfen geçerli bir YouTube URL'si girin."); + return; + } + + try { + const result: any = await createFromYoutube.mutateAsync({ + youtubeUrl, + language, + aspectRatio, + videoStyle: style, + targetDuration: duration, + }); + + toast.success("YouTube → Video projesi oluşturuldu!"); + router.push(`/dashboard/projects/${result.id}`); + } catch (error) { + toast.error("Proje oluşturulurken hata oluştu."); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+

+ YouTube'dan Video Üret +

+

+ YouTube linkini yapıştırın, yapay zeka ana içeriği çıkartıp viral bir Reels/Shorts yaratsın. +

+
+ + {/* Input */} +
+
+ +
+ setYoutubeUrl(e.target.value)} + className="w-full bg-[var(--color-bg-surface)] border-2 border-[var(--color-border-faint)] rounded-2xl py-4 pl-12 pr-4 text-sm + focus:border-red-500/50 focus:ring-4 focus:ring-red-500/10 transition-all outline-none" + /> + +
+
+
+ + {/* Video Settings */} +
+
+ +
+ {languages.map((l) => ( + + ))} +
+
+ +
+ +
+ {videoStyles.map((s) => ( + + ))} +
+
+ +
+
+ + setDuration(Number(e.target.value))} + className="w-full h-1.5 rounded-full bg-[var(--color-bg-elevated)] appearance-none cursor-pointer + [&::-webkit-slider-thumb]:appearance-none + [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 + [&::-webkit-slider-thumb]:rounded-full + [&::-webkit-slider-thumb]:bg-red-500 + [&::-webkit-slider-thumb]:shadow-[0_0_12px_rgba(239,68,68,0.4)] + [&::-webkit-slider-thumb]:cursor-grab" + /> +
+ +
+ +
+ {aspectRatios.map((ar) => { + const Icon = ar.icon; + return ( + + ); + })} +
+
+
+ + + +

+ Bu işlem 1 kredi kullanır • AI senaryo + görsel üretim dahil +

+
+ +
+ ); +} diff --git a/src/components/layout/app-shell.tsx b/src/components/layout/app-shell.tsx index eba1f1b..3e40ed1 100644 --- a/src/components/layout/app-shell.tsx +++ b/src/components/layout/app-shell.tsx @@ -2,7 +2,7 @@ import { usePathname } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; -import { Home, FolderOpen, LayoutGrid, Settings, Sparkles, AtSign, ShieldCheck, LogOut } from "lucide-react"; +import { Home, FolderOpen, LayoutGrid, Settings, Sparkles, AtSign, ShieldCheck, LogOut, Link2, FileText } from "lucide-react"; import Link from "next/link"; import { cn } from "@/lib/utils"; import { useCreditBalance, useCurrentUser } from "@/hooks/use-api"; @@ -13,6 +13,8 @@ const navItems = [ { href: "/dashboard", icon: Home, label: "Ana Sayfa" }, { href: "/dashboard/projects", icon: FolderOpen, label: "Projeler" }, { href: "/dashboard/x-to-video", icon: AtSign, label: "X → Video" }, + { href: "/dashboard/youtube-to-video", icon: Link2, label: "YT → Video" }, + { href: "/dashboard/document-to-video", icon: FileText, label: "Belge → Video" }, { href: "/dashboard/templates", icon: LayoutGrid, label: "Şablonlar" }, { href: "/dashboard/settings", icon: Settings, label: "Ayarlar" }, ]; diff --git a/src/components/project/scene-card.tsx b/src/components/project/scene-card.tsx index 05bf20c..c70c4b7 100644 --- a/src/components/project/scene-card.tsx +++ b/src/components/project/scene-card.tsx @@ -242,7 +242,7 @@ export function SceneCard({