forked from 77media/video-flow
125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { getScriptEpisodeListNew, MovieProject } from '@/api/script_episode';
|
|
import { getFirstFrame } from '@/utils/tools';
|
|
import { useRouter } from 'next/navigation';
|
|
import { motion } from 'framer-motion';
|
|
|
|
/**
|
|
* A lightweight horizontal list of user's movie projects.
|
|
* Shows first 9 items, clipped horizontally, with a right-side gradient more button.
|
|
*/
|
|
const MyMovies: React.FC = () => {
|
|
const [projects, setProjects] = useState<MovieProject[]>([]);
|
|
const router = useRouter();
|
|
|
|
const StatusBadge = (status: string): JSX.Element => {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4 }}
|
|
className="flex items-center"
|
|
data-alt="status-badge"
|
|
>
|
|
{status === 'pending' && (
|
|
<>
|
|
<motion.span
|
|
className="w-2 h-2 rounded-full bg-yellow-400 shadow-[0_0_8px_rgba(255,220,100,0.9)]"
|
|
animate={{ scale: [1, 1.4, 1] }}
|
|
transition={{ repeat: Infinity, duration: 1.5 }}
|
|
/>
|
|
</>
|
|
)}
|
|
{status === 'failed' && (
|
|
<>
|
|
<motion.span
|
|
className="w-2 h-2 rounded-full bg-red-500 shadow-[0_0_8px_rgba(255,0,80,0.9)]"
|
|
/>
|
|
<span className="ml-1 text-xs tracking-widest text-red-400 font-medium drop-shadow-[0_0_6px_rgba(255,0,80,0.6)]">
|
|
FAILED
|
|
</span>
|
|
</>
|
|
)}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const user = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
|
const userId = String(user.id || '0');
|
|
getScriptEpisodeListNew({ user_id: userId, page: 1, per_page: 9 })
|
|
.then((res) => {
|
|
if ((res as any)?.code === 0) {
|
|
setProjects((res as any).data.movie_projects?.slice(0, 9) || []);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}, []);
|
|
|
|
return (
|
|
<section data-alt="my-movies" className="w-full">
|
|
<div data-alt="my-movies-header" className="mb-4 flex items-center justify-between">
|
|
<h2 data-alt="my-movies-title" className="text-xl py-4 font-semibold text-white">My Movies</h2>
|
|
<Link data-alt="all-movies-link" href="/movies" className="text-sm px-2 border rounded-full text-blue-400 hover:text-blue-300">All movies →</Link>
|
|
</div>
|
|
|
|
<div data-alt="my-movies-row" className="w-full flex items-stretch gap-4">
|
|
<div data-alt="movies-scroll" className="flex-1 overflow-hidden relative">
|
|
<div data-alt="movies-list" className="flex gap-4">
|
|
{projects.map((p) => (
|
|
<button
|
|
type="button"
|
|
key={p.project_id}
|
|
data-alt="movie-item"
|
|
className="w-48 flex-shrink-0 text-left"
|
|
onMouseEnter={(e) => {
|
|
const v = e.currentTarget.querySelector('video');
|
|
if (v) {
|
|
// @ts-ignore
|
|
v.play?.();
|
|
}
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
const v = e.currentTarget.querySelector('video');
|
|
if (v) {
|
|
// @ts-ignore
|
|
v.pause?.();
|
|
// @ts-ignore
|
|
v.currentTime = 0;
|
|
}
|
|
}}
|
|
onClick={() => router.push(`/movies/work-flow?episodeId=${p.project_id}`)}
|
|
>
|
|
<div data-alt="movie-thumb" className="w-48 h-28 rounded-lg overflow-hidden border border-white/10 bg-white/5 relative">
|
|
<video
|
|
src={p.final_video_url || p.final_simple_video_url || p.video_urls || ''}
|
|
className="w-full h-full object-cover object-center"
|
|
playsInline
|
|
muted
|
|
preload="none"
|
|
poster={p.video_snapshot_url || getFirstFrame(p.final_video_url || p.final_simple_video_url || p.video_urls || '', 300, p.aspect_ratio)}
|
|
/>
|
|
<div data-alt="status-overlay" className="absolute top-2 left-2">
|
|
{StatusBadge((p.status === 'COMPLETED' || p.final_simple_video_url) ? 'completed' : p.status === 'FAILED' ? 'failed' : 'pending')}
|
|
</div>
|
|
</div>
|
|
<div data-alt="movie-name" className="mt-2 text-sm text-white/90 truncate">{p.name}</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div data-alt="mask" className="absolute right-0 top-0 w-20 h-full bg-gradient-to-r from-transparent to-black"></div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default MyMovies;
|
|
|
|
|