video-flow-b/components/FamousTemplate.tsx
2025-10-17 15:43:40 +08:00

191 lines
7.9 KiB
TypeScript

"use client"
import type React from "react"
import { useEffect, useState } from "react"
import Link from "next/link"
import { X } from "lucide-react"
import { PcTemplateModal } from "@/components/ChatInputBox/PcTemplateModal"
import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService"
/**
* A compact template showcase with a header and link to all templates.
* Shows first 12 templates, in 3 columns, each with thumbnail, name and brief.
* @returns {JSX.Element} - FamousTemplate component
*/
const FamousTemplate: React.FC = () => {
const { templateStoryList, getTemplateStoryList, isLoading } = useTemplateStoryServiceHook()
const [isModalOpen, setIsModalOpen] = useState(false)
const [initialTemplateId, setInitialTemplateId] = useState<string | undefined>(undefined)
const [isTemplateCreating, setIsTemplateCreating] = useState(false)
const [isRoleGenerating, setIsRoleGenerating] = useState<{ [key: string]: boolean }>({})
const [isItemGenerating, setIsItemGenerating] = useState<{ [key: string]: boolean }>({})
const [activeTemplateId, setActiveTemplateId] = useState<string | null>(null)
const [isPreviewReady, setIsPreviewReady] = useState(false)
useEffect(() => {
void getTemplateStoryList()
}, [getTemplateStoryList])
const topTemplates = templateStoryList.slice(0, 10)
return (
<section data-alt="famous-template" className="w-full">
<div data-alt="famous-template-header" className="mb-4 flex items-center justify-between">
<h2 data-alt="famous-template-title" className="text-xl py-4 font-semibold text-white">
Make Movie
</h2>
</div>
{isLoading ? (
<div data-alt="loading" className="text-sm text-white/70">
Loading...
</div>
) : (
<div data-alt="template-grid" className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-5 gap-8">
{topTemplates.map((t) => {
return (
<div data-alt="template-item" key={t.id} className="relative h-40 group">
<div
data-alt="template-item-placeholder"
className="flex items-center gap-3 rounded-lg border border-white/10 bg-white/0 h-full transition-transform duration-300 ease-out group-hover:scale-105"
>
<div
data-alt="template-thumb"
className="w-full h-full rounded-md overflow-hidden border border-white/10 flex-shrink-0"
>
<img
src={t.image_url?.[0] || ""}
alt={t.name}
className="w-full h-full object-cover object-center"
/>
<div data-alt="template-meta" className="flex-1 min-w-0 absolute bg-black/50 bottom-0 left-0 right-0 px-3 py-3">
<div data-alt="template-name" className="text-base font-bold text-white truncate">{t.name}</div>
</div>
</div>
</div>
<div
data-alt="template-item-click-layer"
className="absolute inset-0 cursor-pointer"
onClick={() => {
const id = t.id || t.template_id
if (t.show_url) {
if (activeTemplateId === id) {
setActiveTemplateId(null)
} else {
setIsPreviewReady(false)
setActiveTemplateId(id)
}
} else {
setInitialTemplateId(id)
setIsModalOpen(true)
}
}}
/>
</div>
)
})}
</div>
)}
{/* Centered modal for active template preview */}
{activeTemplateId && (() => {
const active = topTemplates.find((x) => (x.id || x.template_id) === activeTemplateId)
if (!active || !active.show_url) return null
return (
<div
data-alt="template-preview-modal"
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4"
onClick={() => setActiveTemplateId(null)}
>
<div
data-alt="template-preview-modal-content"
className="relative w-[80vw] min-h-[40vw] rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<>
<video
src={active.show_url}
autoPlay
loop
muted
playsInline
onCanPlay={() => setIsPreviewReady(true)}
className={`w-full h-auto transition-opacity duration-200 ${isPreviewReady ? "opacity-100" : "opacity-0"}`}
/>
{!isPreviewReady && (
<div
data-alt="template-preview-loading"
className="absolute inset-0 flex items-center justify-center"
>
<div className="h-10 w-10 rounded-full border-2 border-white/30 border-t-white/80 animate-spin" />
</div>
)}
{/* template-preview-header */}
{isPreviewReady && (
<div
data-alt="template-preview-header"
className="absolute top-0 left-0 right-0 bg-gradient-to-b from-black/90 via-black/80 to-transparent px-6 py-8 text-center"
>
<div className="text-bold text-2xl text-white/90 line-clamp-2 text-left mb-4">
{active.name}
</div>
</div>
)}
{isPreviewReady && (
<div
data-alt="template-preview-footer"
className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 via-black/80 to-transparent px-6 py-8 text-center"
>
<div className="text-base text-white/90 line-clamp-2 text-left mb-4">
{active.generateText || active.name}
</div>
<button
type="button"
className="items-center text-bold justify-center rounded-full border border-white/30 bg-white/10 px-6 py-2 text-sm text-xl text-white transition hover:border-white hover:bg-white hover:text-slate-900"
onClick={() => {
setInitialTemplateId(active.id || active.template_id)
setIsModalOpen(true)
setActiveTemplateId(null)
}}
data-alt="template-preview-try-it"
>
Try it Free
</button>
</div>
)}
<button
type="button"
data-alt="template-preview-modal-close"
className="absolute top-3 right-3 inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 p-2 text-white hover:border-white hover:bg-white hover:text-slate-900"
onClick={() => setActiveTemplateId(null)}
>
<X className="h-4 w-4" />
</button>
</>
</div>
</div>
)
})()}
<PcTemplateModal
isTemplateCreating={isTemplateCreating}
setIsTemplateCreating={setIsTemplateCreating}
isRoleGenerating={isRoleGenerating}
setIsRoleGenerating={setIsRoleGenerating}
isItemGenerating={isItemGenerating}
setIsItemGenerating={setIsItemGenerating}
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
initialTemplateId={initialTemplateId}
configOptions={{ mode: "auto", resolution: "720p", language: "english", videoDuration: "auto" }}
/>
</section>
)
}
export default FamousTemplate