diff --git a/src/app/[locale]/templates/[id]/page.tsx b/src/app/[locale]/templates/[id]/page.tsx index c8ca660..3872de3 100644 --- a/src/app/[locale]/templates/[id]/page.tsx +++ b/src/app/[locale]/templates/[id]/page.tsx @@ -2,13 +2,16 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { TemplateDetailContent } from "@/components/templates/TemplateDetailContent"; -import { fetchProject } from "@/lib/admin-api"; +import { fetchProject, fetchTemplateVariants } from "@/lib/admin-api"; import { adminProjectToCatalogTemplate, VIDEO_TEMPLATES_CATALOG, + type TemplateDetailAspectRatio, type VideoCatalogTemplate, } from "@/lib/video-templates-catalog"; +const SUPPORTED_ASPECTS = new Set(["16:9", "1:1", "9:16"]); + interface TemplateDetailPageProps { params: Promise<{ id: string }>; } @@ -20,7 +23,13 @@ interface TemplateDetailPageProps { */ async function resolveTemplate(id: string): Promise { const admin = await fetchProject(id); - if (admin) return adminProjectToCatalogTemplate(admin); + if (admin) { + const base = adminProjectToCatalogTemplate(admin); + const variants = (await fetchTemplateVariants(id)) + .filter((v) => SUPPORTED_ASPECTS.has(v.aspect as TemplateDetailAspectRatio)) + .map((v) => ({ aspect: v.aspect as TemplateDetailAspectRatio, projectId: v.projectId })); + return { ...base, variants }; + } return VIDEO_TEMPLATES_CATALOG.find((item) => item.id === id) ?? null; } diff --git a/src/components/templates/TemplateDetailContent.tsx b/src/components/templates/TemplateDetailContent.tsx index e19ab61..0af1f95 100644 --- a/src/components/templates/TemplateDetailContent.tsx +++ b/src/components/templates/TemplateDetailContent.tsx @@ -1,23 +1,39 @@ "use client"; +import { useState } from "react"; + import { TemplateDetailBreadcrumb } from "@/components/templates/TemplateDetailBreadcrumb"; import { TemplateDetailExamples } from "@/components/templates/TemplateDetailExamples"; import { TemplateDetailInfo } from "@/components/templates/TemplateDetailInfo"; import { TemplateDetailPreview } from "@/components/templates/TemplateDetailPreview"; -import type { VideoCatalogTemplate } from "@/lib/video-templates-catalog"; +import { + getTemplateDetailAspectRatios, + type TemplateDetailAspectRatio, + type VideoCatalogTemplate, +} from "@/lib/video-templates-catalog"; interface TemplateDetailContentProps { template: VideoCatalogTemplate; } export function TemplateDetailContent({ template }: TemplateDetailContentProps) { + // Selected aspect is shared so the preview picker drives which variant gets built. + const aspects = getTemplateDetailAspectRatios(template); + const [selectedAspect, setSelectedAspect] = useState( + aspects[0] ?? "16:9" + ); + return (
- - + +
diff --git a/src/components/templates/TemplateDetailInfo.tsx b/src/components/templates/TemplateDetailInfo.tsx index a6fc418..3e420b3 100644 --- a/src/components/templates/TemplateDetailInfo.tsx +++ b/src/components/templates/TemplateDetailInfo.tsx @@ -16,6 +16,7 @@ import { getVideoTemplateStyleImageSrc, TEMPLATE_STYLE_COUNT, toProjectTemplate, + type TemplateDetailAspectRatio, type VideoCatalogTemplate, } from "@/lib/video-templates-catalog"; import { cn } from "@/lib/utils"; @@ -29,9 +30,10 @@ const STYLE_LABEL_KEYS = [ interface TemplateDetailInfoProps { template: VideoCatalogTemplate; + selectedAspect: TemplateDetailAspectRatio; } -export function TemplateDetailInfo({ template }: TemplateDetailInfoProps) { +export function TemplateDetailInfo({ template, selectedAspect }: TemplateDetailInfoProps) { const t = useTranslations("auto.componentsTemplatesTemplateDetailInfo"); const router = useRouter(); const [selectedStyle, setSelectedStyle] = useState(0); @@ -44,7 +46,14 @@ export function TemplateDetailInfo({ template }: TemplateDetailInfoProps) { const handleCreate = async () => { setIsCreating(true); - const result = await createProjectFromTemplate(toProjectTemplate(template)); + // Build the variant matching the selected aspect (its content project id); fall + // back to the template id (slug) which the API resolves to a default variant. + const variant = template.variants?.find((v) => v.aspect === selectedAspect); + const base = toProjectTemplate(template); + const result = await createProjectFromTemplate({ + ...base, + id: variant?.projectId ?? base.id, + }); if (!result.ok) { setIsCreating(false); @@ -73,6 +82,8 @@ export function TemplateDetailInfo({ template }: TemplateDetailInfoProps) { {categoryLabel} {durationLabel} + + {selectedAspect}
diff --git a/src/components/templates/TemplateDetailPreview.tsx b/src/components/templates/TemplateDetailPreview.tsx index 3880da7..950dd31 100644 --- a/src/components/templates/TemplateDetailPreview.tsx +++ b/src/components/templates/TemplateDetailPreview.tsx @@ -17,19 +17,35 @@ import { cn } from "@/lib/utils"; interface TemplateDetailPreviewProps { template: VideoCatalogTemplate; + selectedAspect: TemplateDetailAspectRatio; + onSelectAspect: (aspect: TemplateDetailAspectRatio) => void; } -export function TemplateDetailPreview({ template }: TemplateDetailPreviewProps) { +const ASPECT_BOX: Record = { + "16:9": "aspect-video", + "1:1": "aspect-square mx-auto max-w-md", + "9:16": "aspect-[9/16] mx-auto max-w-[300px]", +}; + +export function TemplateDetailPreview({ + template, + selectedAspect, + onSelectAspect, +}: TemplateDetailPreviewProps) { const t = useTranslations("auto.componentsTemplatesTemplateDetailPreview"); const [isPlaying, setIsPlaying] = useState(false); - const [selectedRatio, setSelectedRatio] = useState("16:9"); const aspectOptions = getTemplateDetailAspectRatios(template); const posterSrc = getVideoTemplateImageSrc(template.id); const videoSrc = getTemplatePreviewVideoSrc(template.id); return (
-
+
{isPlaying ? (