Files
ContentGen_FE/src/app/[locale]/(dashboard)/dashboard/document-to-video/page.tsx
T
Harun CAN 50b2c0d8af
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
main
2026-04-12 15:15:43 +02:00

253 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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<File | null>(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<HTMLInputElement>) => {
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 (
<div className="max-w-3xl mx-auto space-y-8 pb-24">
{/* Header */}
<div className="text-center space-y-3 pb-4">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-3xl bg-blue-500/10 text-blue-500 mb-2 ring-1 ring-blue-500/20 shadow-[0_0_30px_rgba(59,130,246,0.15)]">
<FileText size={32} />
</div>
<h1 className="font-[family-name:var(--font-display)] text-3xl md:text-4xl font-bold tracking-tight text-[var(--color-text-primary)]">
Belgeden Video Üret
</h1>
<p className="text-[var(--color-text-muted)] text-sm max-w-lg mx-auto">
PDF, Word, TXT vb. belgenizi yükleyin, yapay zeka içeriği tarayıp senaryolastirsin.
</p>
</div>
{/* Input */}
<div className="card p-6 md:p-8 space-y-4">
<div>
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-2 block">
Belge Yükle (PDF, DOCX, TXT)
</label>
<div className="relative">
<input
type="file"
onChange={handleFileChange}
accept=".pdf,.docx,.txt,.csv,.xlsx,.pptx"
className="block w-full text-sm text-[var(--color-text-muted)]
file:mr-4 file:py-3 file:px-4
file:rounded-xl file:border-0
file:text-sm file:font-semibold
file:bg-blue-500/10 file:text-blue-500
hover:file:bg-blue-500/20 cursor-pointer"
/>
</div>
</div>
</div>
{/* Video Settings */}
<div className="space-y-6">
<div className="card p-5 space-y-3">
<label className="text-sm font-medium text-[var(--color-text-secondary)]">
Video Dili
</label>
<div className="flex gap-2">
{languages.map((l) => (
<button
key={l.code}
onClick={() => setLanguage(l.code)}
className={cn(
"flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs transition-all",
language === l.code
? "bg-blue-500/12 border border-blue-500/30 text-blue-400"
: "bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-[var(--color-text-muted)]",
)}
>
<span>{l.flag}</span>
{l.label}
</button>
))}
</div>
</div>
<div className="card p-5 space-y-3">
<label className="text-sm font-medium text-[var(--color-text-secondary)]">
<Palette size={14} className="inline mr-1.5 text-blue-400" />
Video Stili
</label>
<div className="flex flex-wrap gap-2">
{videoStyles.map((s) => (
<button
key={s.id}
onClick={() => setStyle(s.id)}
className={cn(
"flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs transition-all",
style === s.id
? "bg-blue-500/12 border border-blue-500/30 text-blue-400"
: "bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-[var(--color-text-muted)]",
)}
>
<span>{s.emoji}</span>
{s.label}
</button>
))}
</div>
</div>
<div className="card p-5 space-y-4">
<div>
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 block">
<Clock size={14} className="inline mr-1.5 text-cyan-400" />
Hedef Süre: <span className="text-blue-400">{duration}s</span>
</label>
<input
type="range"
min={15}
max={120}
step={5}
value={duration}
onChange={(e) => 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"
/>
</div>
<div>
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 block">
En-Boy Oranı
</label>
<div className="flex gap-2">
{aspectRatios.map((ar) => {
const Icon = ar.icon;
return (
<button
key={ar.id}
onClick={() => setAspectRatio(ar.id)}
className={cn(
"flex-1 flex flex-col items-center gap-1.5 py-3 rounded-xl text-xs transition-all",
aspectRatio === ar.id
? "bg-blue-500/12 border border-blue-500/30 text-blue-400"
: "bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-[var(--color-text-muted)]",
)}
>
<Icon size={20} />
<span className="font-semibold">{ar.label}</span>
<span className="text-[10px] text-[var(--color-text-ghost)]">
{ar.desc}
</span>
</button>
);
})}
</div>
</div>
</div>
<button
onClick={handleGenerate}
disabled={createFromDocument.isPending || !file}
className={cn(
"w-full py-4 rounded-xl font-semibold text-base flex items-center justify-center gap-2 transition-all",
createFromDocument.isPending
? "bg-blue-500/20 text-blue-400 cursor-wait"
: "bg-blue-500 hover:bg-blue-600 text-white shadow-lg shadow-blue-500/20",
!file && "opacity-50 cursor-not-allowed"
)}
>
{createFromDocument.isPending ? (
<>
<Loader2 size={20} className="animate-spin" />
<span>Video Projesi Oluşturuluyor... (Bu işlem uzun sürebilir)</span>
</>
) : (
<>
<Wand2 size={20} />
<span>Belge Video Oluştur</span>
<ArrowRight size={16} />
</>
)}
</button>
<p className="text-center text-[11px] text-[var(--color-text-ghost)]">
Bu işlem 1 kredi kullanır AI senaryo + görsel üretim dahil
</p>
</div>
</div>
);
}