main
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-04-30 13:46:23 +02:00
parent 5144ee4d9a
commit 1b980f637b
3 changed files with 439 additions and 128 deletions
@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
import { motion } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { import {
ArrowLeft, ArrowLeft,
Play, Play,
@@ -16,6 +16,7 @@ import {
Trash2, Trash2,
MoreVertical, MoreVertical,
X, X,
Languages,
} from 'lucide-react'; } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
@@ -34,8 +35,10 @@ import { useRenderProgress } from '@/hooks/use-render-progress';
import { SceneCard } from '@/components/project/scene-card'; import { SceneCard } from '@/components/project/scene-card';
import { RenderProgress } from '@/components/project/render-progress'; import { RenderProgress } from '@/components/project/render-progress';
import { VideoPlayer } from '@/components/project/video-player'; import { VideoPlayer } from '@/components/project/video-player';
import { projectsApi } from '@/lib/api/api-service'; import { projectsApi, apiClient } from '@/lib/api/api-service';
import { CINEMATIC_REFERENCES } from '@/constants/cinematic-references'; import { CINEMATIC_REFERENCES } from '@/constants/cinematic-references';
import { languages } from '@/components/projects/ProjectConfiguration';
import { toast } from 'sonner';
// X (Twitter) ikonunu burada da tanımlıyoruz // X (Twitter) ikonunu burada da tanımlıyoruz
const XIcon = ({ size = 16 }: { size?: number }) => ( const XIcon = ({ size = 16 }: { size?: number }) => (
@@ -127,6 +130,27 @@ export default function ProjectDetailPage() {
const [regeneratingSceneId, setRegeneratingSceneId] = useState<string | null>(null); const [regeneratingSceneId, setRegeneratingSceneId] = useState<string | null>(null);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [showTranslateModal, setShowTranslateModal] = useState(false);
const [targetLanguage, setTargetLanguage] = useState<string>("");
const [isTranslating, setIsTranslating] = useState(false);
const confirmTranslate = async () => {
if (!id || !targetLanguage) return;
try {
setIsTranslating(true);
const res = await apiClient.post(`/projects/${id}/translate`, { targetLanguage });
toast.success("Proje başarıyla çevrildi!");
setShowTranslateModal(false);
setTargetLanguage("");
// refetch() to maybe update some states, or router.push to the new project
router.push(`/dashboard/projects/${res.data.id}`);
} catch (err: any) {
toast.error(err?.response?.data?.message || "Çeviri sırasında bir hata oluştu.");
} finally {
setIsTranslating(false);
}
};
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
}, []); }, []);
@@ -328,6 +352,12 @@ export default function ProjectDetailPage() {
> >
<RefreshCw size={14} /> Yenile <RefreshCw size={14} /> Yenile
</button> </button>
<button
onClick={() => { setShowMenu(false); setShowTranslateModal(true); }}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-elevated)] rounded-lg transition-colors"
>
<Languages size={14} /> Çevir
</button>
<button <button
onClick={() => { handleDelete(); setShowMenu(false); }} onClick={() => { handleDelete(); setShowMenu(false); }}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-red-400 hover:bg-red-500/10 rounded-lg transition-colors" className="w-full flex items-center gap-2 px-3 py-2 text-sm text-red-400 hover:bg-red-500/10 rounded-lg transition-colors"
@@ -672,6 +702,93 @@ export default function ProjectDetailPage() {
</div> </div>
</motion.div> </motion.div>
)} )}
{/* ─── Çeviri Modal ─── */}
<AnimatePresence>
{showTranslateModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
onClick={() => !isTranslating && setShowTranslateModal(false)}
>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
className="relative bg-[var(--color-bg-elevated)] border border-[var(--color-border-default)] rounded-2xl p-6 max-w-sm w-full mx-4 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Kapatma butonu */}
<button
onClick={() => setShowTranslateModal(false)}
disabled={isTranslating}
className="absolute top-3 right-3 p-1 rounded-lg text-[var(--color-text-ghost)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
>
<X size={16} />
</button>
{/* Icon */}
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-blue-500/10 mb-4">
<Languages size={22} className="text-blue-400" />
</div>
{/* İçerik */}
<h3 className="text-base font-semibold text-[var(--color-text-primary)] mb-1.5">
Projeyi Çevir
</h3>
<p className="text-sm text-[var(--color-text-muted)] mb-3">
"{project?.title}" projesini başka bir dile çevirin. (1 kredi)
</p>
<select
value={targetLanguage}
onChange={(e) => setTargetLanguage(e.target.value)}
disabled={isTranslating}
className="w-full bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-xl px-4 py-2.5 text-sm text-[var(--color-text-primary)] mb-5 outline-none focus:border-blue-500"
>
<option value="">Dil Seçin...</option>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.label}
</option>
))}
</select>
{/* Butonlar */}
<div className="flex items-center gap-3">
<button
onClick={() => setShowTranslateModal(false)}
disabled={isTranslating}
className="flex-1 px-4 py-2.5 rounded-xl border border-[var(--color-border-default)] text-sm font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
>
İptal
</button>
<button
onClick={confirmTranslate}
disabled={isTranslating || !targetLanguage}
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isTranslating ? (
<>
<Loader2 size={14} className="animate-spin" />
Çevriliyor...
</>
) : (
<>
<Languages size={14} />
Çevir (1 🪙)
</>
)}
</button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</motion.div> </motion.div>
); );
} }
@@ -14,10 +14,16 @@ import {
Loader2, Loader2,
Trash2, Trash2,
X, X,
Languages,
ChevronDown,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useProjects, useDeleteProject } from "@/hooks/use-api"; import { useProjects, useDeleteProject } from "@/hooks/use-api";
import { languages } from "@/components/projects/ProjectConfiguration";
import { toast } from "sonner";
import { apiClient } from "@/lib/api/api-service";
const statusFilters = [ const statusFilters = [
{ id: "all", label: "Tümü" }, { id: "all", label: "Tümü" },
@@ -79,16 +85,46 @@ interface ProjectItem {
language?: string; language?: string;
progress?: number; progress?: number;
creditsUsed?: number; creditsUsed?: number;
parentId?: string | null;
} }
export default function ProjectsPage() { export default function ProjectsPage() {
const router = useRouter();
const [activeFilter, setActiveFilter] = useState("all"); const [activeFilter, setActiveFilter] = useState("all");
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [deleteTarget, setDeleteTarget] = useState<{ id: string; title: string } | null>(null); const [deleteTarget, setDeleteTarget] = useState<{ id: string; title: string } | null>(null);
const [expandedProjects, setExpandedProjects] = useState<Record<string, boolean>>({});
const { data, isLoading } = useProjects({ limit: 100 }); const [translateTarget, setTranslateTarget] = useState<ProjectItem | null>(null);
const [targetLanguage, setTargetLanguage] = useState<string>("");
const [isTranslating, setIsTranslating] = useState(false);
const { data, isLoading, refetch } = useProjects({ limit: 100 });
const deleteMutation = useDeleteProject(); const deleteMutation = useDeleteProject();
const confirmTranslate = async () => {
if (!translateTarget || !targetLanguage) return;
try {
setIsTranslating(true);
const res = await apiClient.post(`/projects/${translateTarget.id}/translate`, { targetLanguage });
toast.success("Proje başarıyla çevrildi!");
setTranslateTarget(null);
setTargetLanguage("");
refetch();
// Yönlendirmek istersen: router.push(`/dashboard/projects/${res.data.id}`);
} catch (err: any) {
toast.error(err?.response?.data?.message || "Çeviri sırasında bir hata oluştu.");
} finally {
setIsTranslating(false);
}
};
const toggleExpand = useCallback((e: React.MouseEvent, id: string) => {
e.preventDefault();
e.stopPropagation();
setExpandedProjects((prev) => ({ ...prev, [id]: !prev[id] }));
}, []);
// Silme onay modal'ını aç (native confirm yerine) // Silme onay modal'ını aç (native confirm yerine)
const openDeleteConfirm = useCallback((e: React.MouseEvent, project: ProjectItem) => { const openDeleteConfirm = useCallback((e: React.MouseEvent, project: ProjectItem) => {
e.preventDefault(); e.preventDefault();
@@ -125,6 +161,21 @@ export default function ProjectsPage() {
}); });
}, [projects, activeFilter, searchQuery]); }, [projects, activeFilter, searchQuery]);
const rootProjects = useMemo(() => {
return filtered.filter(p => !p.parentId);
}, [filtered]);
const childrenMap = useMemo(() => {
const map: Record<string, ProjectItem[]> = {};
filtered.forEach(p => {
if (p.parentId) {
if (!map[p.parentId]) map[p.parentId] = [];
map[p.parentId].push(p);
}
});
return map;
}, [filtered]);
return ( return (
<div className="max-w-5xl mx-auto space-y-6"> <div className="max-w-5xl mx-auto space-y-6">
{/* Başlık */} {/* Başlık */}
@@ -228,145 +279,276 @@ export default function ProjectsPage() {
)} )}
</div> </div>
) : ( ) : (
filtered.map((project) => { rootProjects.map((project) => {
const st = statusMap[project.status] ?? statusMap.draft; const children = childrenMap[project.id] || [];
const StIcon = st.icon; const hasChildren = children.length > 0;
return ( const isExpanded = expandedProjects[project.id];
<div
key={project.id}
className="flex items-center rounded-xl card hover:border-neutral-400 dark:hover:border-neutral-600 transition-all group relative"
>
<Link
href={`/dashboard/projects/${project.id}`}
className="flex items-center gap-4 p-4 flex-1 min-w-0"
>
<div
className={`w-10 h-10 rounded-xl ${st.bgColor} flex items-center justify-center shrink-0 ${st.color}`}
>
<StIcon size={18} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-[var(--color-text-primary)] truncate group-hover:text-[var(--color-text-secondary)] transition-colors">
{project.title}
</p>
<div className="flex items-center gap-3 mt-0.5 text-[11px] text-[var(--color-text-ghost)]">
<span>
{new Date(project.createdAt).toLocaleDateString(
"tr-TR",
{
day: "numeric",
month: "short",
year: "numeric",
},
)}
</span>
{project.language && <span> {project.language}</span>}
{typeof project.creditsUsed === "number" &&
project.creditsUsed > 0 && (
<span> {project.creditsUsed} kredi</span>
)}
</div>
</div>
<span
className={`text-[10px] font-medium px-2.5 py-1 rounded-full border ${st.color} border-current/20 ${st.bgColor} shrink-0 mr-2`}
>
{st.label}
</span>
<ExternalLink
size={14}
className="text-[var(--color-text-ghost)] group-hover:text-[var(--color-text-primary)] transition-colors shrink-0"
/>
</Link>
<button const renderCard = (p: ProjectItem, isChild = false) => {
onClick={(e) => openDeleteConfirm(e, project)} const st = statusMap[p.status] ?? statusMap.draft;
className="p-2 rounded-lg text-[var(--color-text-ghost)] hover:text-red-400 hover:bg-red-500/10 transition-colors shrink-0 z-10 mr-3" const StIcon = st.icon;
title="Projeyi Sil" return (
<div
key={p.id}
className={cn(
"flex items-center rounded-xl card hover:border-neutral-400 dark:hover:border-neutral-600 transition-all group relative",
isChild && "bg-[var(--color-bg-surface)]"
)}
> >
<Trash2 size={16} /> <Link
</button> href={`/dashboard/projects/${p.id}`}
className="flex items-center gap-4 p-4 flex-1 min-w-0"
>
<div
className={`w-10 h-10 rounded-xl ${st.bgColor} flex items-center justify-center shrink-0 ${st.color}`}
>
<StIcon size={18} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-[var(--color-text-primary)] truncate group-hover:text-[var(--color-text-secondary)] transition-colors">
{p.title}
</p>
<div className="flex items-center gap-3 mt-0.5 text-[11px] text-[var(--color-text-ghost)]">
<span>
{new Date(p.createdAt).toLocaleDateString(
"tr-TR",
{
day: "numeric",
month: "short",
year: "numeric",
},
)}
</span>
{p.language && <span> {p.language}</span>}
{typeof p.creditsUsed === "number" &&
p.creditsUsed > 0 && (
<span> {p.creditsUsed} kredi</span>
)}
</div>
</div>
<span
className={`text-[10px] font-medium px-2.5 py-1 rounded-full border ${st.color} border-current/20 ${st.bgColor} shrink-0 mr-2`}
>
{st.label}
</span>
<ExternalLink
size={14}
className="text-[var(--color-text-ghost)] group-hover:text-[var(--color-text-primary)] transition-colors shrink-0"
/>
</Link>
<div className="flex items-center">
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setTranslateTarget(p);
}}
className="p-2 rounded-lg text-[var(--color-text-ghost)] hover:text-blue-400 hover:bg-blue-500/10 transition-colors shrink-0 z-10"
title="Projeyi Çevir"
>
<Languages size={16} />
</button>
<button
onClick={(e) => openDeleteConfirm(e, p)}
className="p-2 rounded-lg text-[var(--color-text-ghost)] hover:text-red-400 hover:bg-red-500/10 transition-colors shrink-0 z-10 mx-1"
title="Projeyi Sil"
>
<Trash2 size={16} />
</button>
{!isChild && hasChildren && (
<button
onClick={(e) => toggleExpand(e, p.id)}
className="p-2 rounded-lg text-[var(--color-text-ghost)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-surface)] transition-colors shrink-0 z-10 mr-1"
title="Çevirileri Göster"
>
<ChevronDown size={16} className={cn("transition-transform", isExpanded && "rotate-180")} />
</button>
)}
</div>
</div> </div>
); );
}) };
)}
</motion.div>
)}
{/* ─── Silme Onay Modal ─── */} return (
<AnimatePresence> <div key={project.id} className="space-y-2">
{deleteTarget && ( {renderCard(project)}
{isExpanded && hasChildren && (
<div className="pl-6 md:pl-10 space-y-2 relative before:absolute before:left-[1.25rem] md:before:left-[2.25rem] before:top-0 before:bottom-0 before:w-px before:bg-neutral-200 dark:before:bg-neutral-800">
{children.map(child => renderCard(child, true))}
</div>
)}
</div>
);
})
)}
</motion.div>
)}
{/* ─── Silme Onay Modal ─── */}
<AnimatePresence>
{deleteTarget && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
onClick={() => !deleteMutation.isPending && setDeleteTarget(null)}
>
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.15 }} transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" className="relative bg-[var(--color-bg-elevated)] border border-[var(--color-border-default)] rounded-2xl p-6 max-w-sm w-full mx-4 shadow-2xl"
onClick={() => !deleteMutation.isPending && setDeleteTarget(null)} onClick={(e) => e.stopPropagation()}
> >
<motion.div {/* Kapatma butonu */}
initial={{ opacity: 0, scale: 0.95, y: 10 }} <button
animate={{ opacity: 1, scale: 1, y: 0 }} onClick={() => setDeleteTarget(null)}
exit={{ opacity: 0, scale: 0.95, y: 10 }} disabled={deleteMutation.isPending}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }} className="absolute top-3 right-3 p-1 rounded-lg text-[var(--color-text-ghost)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
className="relative bg-[var(--color-bg-elevated)] border border-[var(--color-border-default)] rounded-2xl p-6 max-w-sm w-full mx-4 shadow-2xl"
onClick={(e) => e.stopPropagation()}
> >
{/* Kapatma butonu */} <X size={16} />
</button>
{/* Icon */}
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-red-500/10 mb-4">
<Trash2 size={22} className="text-red-400" />
</div>
{/* İçerik */}
<h3 className="text-base font-semibold text-[var(--color-text-primary)] mb-1.5">
Projeyi Sil
</h3>
<p className="text-sm text-[var(--color-text-muted)] mb-1">
Bu projeyi silmek istediğinize emin misiniz?
</p>
<p className="text-xs text-[var(--color-text-ghost)] mb-5 line-clamp-2 italic">
&ldquo;{deleteTarget.title}&rdquo;
</p>
{/* Butonlar */}
<div className="flex items-center gap-3">
<button <button
onClick={() => setDeleteTarget(null)} onClick={() => setDeleteTarget(null)}
disabled={deleteMutation.isPending} disabled={deleteMutation.isPending}
className="absolute top-3 right-3 p-1 rounded-lg text-[var(--color-text-ghost)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50" className="flex-1 px-4 py-2.5 rounded-xl border border-[var(--color-border-default)] text-sm font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
> >
<X size={16} /> İptal
</button> </button>
<button
{/* Icon */} onClick={confirmDelete}
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-red-500/10 mb-4"> disabled={deleteMutation.isPending}
<Trash2 size={22} className="text-red-400" /> className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-red-500 hover:bg-red-600 text-white text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
</div> >
{deleteMutation.isPending ? (
{/* İçerik */} <>
<h3 className="text-base font-semibold text-[var(--color-text-primary)] mb-1.5"> <Loader2 size={14} className="animate-spin" />
Projeyi Sil Siliniyor...
</h3> </>
<p className="text-sm text-[var(--color-text-muted)] mb-1"> ) : (
Bu projeyi silmek istediğinize emin misiniz? <>
</p> <Trash2 size={14} />
<p className="text-xs text-[var(--color-text-ghost)] mb-5 line-clamp-2 italic"> Evet, Sil
&ldquo;{deleteTarget.title}&rdquo; </>
</p> )}
</button>
{/* Butonlar */} </div>
<div className="flex items-center gap-3">
<button
onClick={() => setDeleteTarget(null)}
disabled={deleteMutation.isPending}
className="flex-1 px-4 py-2.5 rounded-xl border border-[var(--color-border-default)] text-sm font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
>
İptal
</button>
<button
onClick={confirmDelete}
disabled={deleteMutation.isPending}
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-red-500 hover:bg-red-600 text-white text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{deleteMutation.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Siliniyor...
</>
) : (
<>
<Trash2 size={14} />
Evet, Sil
</>
)}
</button>
</div>
</motion.div>
</motion.div> </motion.div>
)} </motion.div>
</AnimatePresence> )}
</div> </AnimatePresence>
);
{/* ─── Çeviri Modal ─── */}
<AnimatePresence>
{translateTarget && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
onClick={() => !isTranslating && setTranslateTarget(null)}
>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
className="relative bg-[var(--color-bg-elevated)] border border-[var(--color-border-default)] rounded-2xl p-6 max-w-sm w-full mx-4 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Kapatma butonu */}
<button
onClick={() => setTranslateTarget(null)}
disabled={isTranslating}
className="absolute top-3 right-3 p-1 rounded-lg text-[var(--color-text-ghost)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
>
<X size={16} />
</button>
{/* Icon */}
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-blue-500/10 mb-4">
<Languages size={22} className="text-blue-400" />
</div>
{/* İçerik */}
<h3 className="text-base font-semibold text-[var(--color-text-primary)] mb-1.5">
Projeyi Çevir
</h3>
<p className="text-sm text-[var(--color-text-muted)] mb-3">
"{translateTarget.title}" projesini başka bir dile çevirin. (1 kredi)
</p>
<select
value={targetLanguage}
onChange={(e) => setTargetLanguage(e.target.value)}
disabled={isTranslating}
className="w-full bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-xl px-4 py-2.5 text-sm text-[var(--color-text-primary)] mb-5"
>
<option value="">Dil Seçin...</option>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.label}
</option>
))}
</select>
{/* Butonlar */}
<div className="flex items-center gap-3">
<button
onClick={() => setTranslateTarget(null)}
disabled={isTranslating}
className="flex-1 px-4 py-2.5 rounded-xl border border-[var(--color-border-default)] text-sm font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] transition-colors disabled:opacity-50"
>
İptal
</button>
<button
onClick={confirmTranslate}
disabled={isTranslating || !targetLanguage}
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isTranslating ? (
<>
<Loader2 size={14} className="animate-spin" />
Çevriliyor...
</>
) : (
<>
<Languages size={14} />
Çevir (1 🪙)
</>
)}
</button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
} }
@@ -25,6 +25,18 @@ export const languages = [
{ code: "ar", label: "العربية", flag: "🇸🇦" }, { code: "ar", label: "العربية", flag: "🇸🇦" },
{ code: "pt", label: "Português", flag: "🇧🇷" }, { code: "pt", label: "Português", flag: "🇧🇷" },
{ code: "ja", label: "日本語", flag: "🇯🇵" }, { code: "ja", label: "日本語", flag: "🇯🇵" },
{ code: "hi", label: "हिन्दी", flag: "🇮🇳" },
{ code: "ru", label: "Русский", flag: "🇷🇺" },
{ code: "ko", label: "한국어", flag: "🇰🇷" },
{ code: "it", label: "Italiano", flag: "🇮🇹" },
{ code: "id", label: "Bahasa Indonesia", flag: "🇮🇩" },
{ code: "vi", label: "Tiếng Việt", flag: "🇻🇳" },
{ code: "zh", label: "简体中文", flag: "🇨🇳" },
{ code: "pl", label: "Polski", flag: "🇵🇱" },
{ code: "th", label: "ภาษาไทย", flag: "🇹🇭" },
{ code: "nl", label: "Nederlands", flag: "🇳🇱" },
{ code: "tl", label: "Tagalog", flag: "🇵🇭" },
{ code: "uk", label: "Українська", flag: "🇺🇦" },
]; ];
export const videoStyles = [ export const videoStyles = [