director-ai / client /src /pages /ProjectPage.tsx
algorembrant's picture
Upload 79 files
11f4e50 verified
import { useEffect, useState } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { motion } from 'framer-motion';
import { AppDispatch, RootState } from '../store';
import { fetchProject } from '../store/projectsSlice';
import AITerminal from '../components/AITerminal';
export default function ProjectPage() {
const { id } = useParams<{ id: string }>();
const dispatch = useDispatch<AppDispatch>();
const { current: project } = useSelector((state: RootState) => state.projects);
const [showTerminal, setShowTerminal] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (id) dispatch(fetchProject(id));
}, [id, dispatch]);
if (!project) {
return (
<div className="min-h-screen pt-20 flex items-center justify-center">
<div className="w-8 h-8 border-2 border-gold-500 border-t-transparent rounded-full animate-spin" />
</div>
);
}
return (
<div className="min-h-screen pt-20 pb-12 px-6">
<div className="max-w-6xl mx-auto">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-sm text-light-500 mb-6">
<Link to="/dashboard" className="hover:text-gold-500 transition-colors">Dashboard</Link>
<span>/</span>
<span className="text-light-300">{project.name}</span>
</div>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8"
>
<div>
<h1 className="font-display text-3xl font-bold text-light-100 mb-1">{project.name}</h1>
<p className="text-light-500">
{project.defaultPlatform} -- {project.defaultFormat} -- {project.videos?.length || 0} videos
</p>
</div>
<div className="flex gap-3">
<button onClick={() => setShowTerminal(!showTerminal)} className="btn-ghost text-sm">
<span className="mr-1 font-mono">&gt;_</span> Terminal
</button>
<button
onClick={() => navigate(`/project/${id}/create`)}
className="btn-primary"
id="create-video-btn"
>
Create Video
</button>
</div>
</motion.div>
{/* Terminal */}
{showTerminal && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
className="mb-8 overflow-hidden"
>
<AITerminal />
</motion.div>
)}
{/* Videos */}
{(!project.videos || project.videos.length === 0) ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="card-static text-center py-16"
>
<h3 className="font-display text-xl text-light-100 mb-2">No videos yet</h3>
<p className="text-light-500 mb-6">Create your first video in this project</p>
<button onClick={() => navigate(`/project/${id}/create`)} className="btn-primary">
Create First Video
</button>
</motion.div>
) : (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{project.videos.map((video: any, i: number) => (
<motion.div
key={video._id || i}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
>
<Link to={`/video/${video._id}/preview`} className="card block group">
<div className="flex items-center justify-between mb-3">
<span className={`text-xs px-2 py-1 rounded font-medium ${video.status === 'exported' ? 'bg-green-500/10 text-green-400' :
video.status === 'generating' ? 'bg-gold-500/10 text-gold-500' :
video.status === 'failed' ? 'bg-red-500/10 text-red-400' :
'bg-dark-500 text-light-500'
}`}>
{video.status || 'pending'}
</span>
</div>
<p className="text-sm text-light-400 line-clamp-3">
{video.script?.slice(0, 120) || 'No script yet'}...
</p>
</Link>
</motion.div>
))}
</div>
)}
</div>
</div>
);
}