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

This commit is contained in:
Harun CAN
2026-04-27 12:50:54 +02:00
parent cf12fc3942
commit 89eb9d4dfd
6 changed files with 622 additions and 34 deletions
@@ -18,7 +18,7 @@ import {
X,
} from 'lucide-react';
import Link from 'next/link';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import {
useProject,
useGenerateScript,
@@ -27,7 +27,8 @@ import {
useDeleteProject,
useGenerateSceneImage,
useUpscaleSceneImage,
useRegenerateScene
useRegenerateScene,
useCancelRender
} from '@/hooks/use-api';
import { useRenderProgress } from '@/hooks/use-render-progress';
import { SceneCard } from '@/components/project/scene-card';
@@ -124,12 +125,25 @@ export default function ProjectDetailPage() {
const router = useRouter();
const [showMenu, setShowMenu] = useState(false);
const [regeneratingSceneId, setRegeneratingSceneId] = useState<string | null>(null);
const [mounted, setMounted] = useState(false);
// Veri hook'ları
const { data: project, isLoading, error, refetch } = useProject(id);
useEffect(() => {
setMounted(true);
}, []);
// Veri hook'ları (Aktif işlem varsa 3 saniyede bir polling yap)
const { data: project, isLoading, error, refetch } = useProject(id, {
refetchInterval: (data: any) => {
if (!data) return false;
const isGenerating = data.status === 'GENERATING_MEDIA' ||
data.renderJobs?.some((j: any) => j.status === 'QUEUED' || j.status === 'PROCESSING');
return isGenerating ? 3000 : false;
}
});
const generateScriptMutation = useGenerateScript();
const approveMutation = useApproveAndQueue();
const deleteMutation = useDeleteProject();
const cancelRenderMutation = useCancelRender();
const generateImageMutation = useGenerateSceneImage();
const upscaleImageMutation = useUpscaleSceneImage();
@@ -203,6 +217,15 @@ export default function ProjectDetailPage() {
});
};
// İptal et
const handleCancelRender = () => {
if (confirm('Aktif video üretimini iptal etmek istediğinize emin misiniz?')) {
cancelRenderMutation.mutate(id, {
onSuccess: () => refetch(),
});
}
};
// Sil
const handleDelete = () => {
if (confirm('Bu projeyi silmek istediğinize emin misiniz?')) {
@@ -457,6 +480,22 @@ export default function ProjectDetailPage() {
Onayla & Video Üret
</button>
)}
{/* Üretimi İptal Et (Sadece aktif işlem varsa) */}
{isRendering && (
<button
onClick={handleCancelRender}
disabled={cancelRenderMutation.isPending}
className="flex items-center gap-2 px-4 py-2.5 rounded-xl bg-red-500/10 text-red-400 text-sm font-medium hover:bg-red-500/20 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{cancelRenderMutation.isPending ? (
<Loader2 size={15} className="animate-spin" />
) : (
<X size={15} />
)}
Üretimi İptal Et
</button>
)}
</div>
{/* Hata mesajı — kapatılabilir ve aksiyon butonlu */}
@@ -571,34 +610,63 @@ export default function ProjectDetailPage() {
{/* ── Render Geçmişi ── */}
{project.renderJobs && project.renderJobs.length > 0 && (
<motion.div variants={fadeUp}>
<h2 className="text-sm font-semibold text-[var(--color-text-primary)] mb-3 flex items-center gap-2">
<Clock size={15} className="text-cyan-400" />
Render Geçmişi
</h2>
<div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-semibold text-[var(--color-text-primary)] flex items-center gap-2">
<Clock size={15} className="text-cyan-400" />
Render Geçmişi
</h2>
{project.status === 'GENERATING_MEDIA' || project.renderJobs.some((j: any) => j.status === 'QUEUED' || j.status === 'PROCESSING') ? (
<button
onClick={handleCancelRender}
disabled={cancelRenderMutation.isPending}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-red-500/10 text-red-400 hover:bg-red-500/20 text-xs font-medium transition-colors"
>
{cancelRenderMutation.isPending ? <Loader2 size={13} className="animate-spin" /> : <X size={13} />}
İptal Et
</button>
) : null}
</div>
<div className="space-y-2">
{project.renderJobs.map((job) => (
<div key={job.id} className="card-surface p-3 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className={`w-2 h-2 rounded-full ${
job.status === 'COMPLETED' ? 'bg-emerald-400' :
job.status === 'FAILED' ? 'bg-red-400' :
'bg-amber-400 animate-pulse'
}`} />
<span className="text-xs text-[var(--color-text-secondary)]">
Deneme #{job.attemptNumber}
</span>
<span className="text-[10px] text-[var(--color-text-ghost)]">
{job.status}
</span>
</div>
<div className="flex items-center gap-3 text-[10px] text-[var(--color-text-ghost)]">
{job.processingTimeMs && (
<span>{(job.processingTimeMs / 1000).toFixed(1)}s</span>
)}
<span>
{new Date(job.createdAt).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })}
</span>
<div key={job.id} className="card-surface p-3 flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className={`w-2 h-2 rounded-full ${
job.status === 'COMPLETED' ? 'bg-emerald-400' :
job.status === 'FAILED' ? 'bg-red-400' :
job.status === 'CANCELLED' ? 'bg-slate-400' :
'bg-amber-400 animate-pulse'
}`} />
<span className="text-xs text-[var(--color-text-secondary)]">
Deneme #{job.attemptNumber}
</span>
<span className="text-[10px] text-[var(--color-text-ghost)]">
{job.status}
</span>
</div>
<div className="flex items-center gap-3 text-[10px] text-[var(--color-text-ghost)]">
{job.processingTimeMs && (
<span>{(job.processingTimeMs / 1000).toFixed(1)}s</span>
)}
{mounted ? (
<span>
{new Date(job.createdAt).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })}
</span>
) : (
<span>--:--</span>
)}
</div>
</div>
{(job.status === 'QUEUED' || job.status === 'PROCESSING') && (
<div className="w-full bg-slate-800 rounded-full h-1.5 mt-1 overflow-hidden">
<div
className="bg-amber-400 h-1.5 rounded-full transition-all duration-1000 ease-out relative"
style={{ width: `${job.progress || (job.status === 'QUEUED' ? 5 : 50)}%` }}
>
<div className="absolute inset-0 bg-white/20 animate-pulse" />
</div>
</div>
)}
</div>
))}
</div>