fix(templates): wire template detail page to real content service
/templates/[id] only searched the hardcoded demo catalog, so real published containers (e.g. insta-promo) 404'd even though the browser listed and linked them. Now resolveTemplate() fetches the container by slug via fetchProject(), falling back to the demo catalog, else notFound(). Page + generateMetadata made async (await params). Also fix TemplateDetailBreadcrumb: it called server-only getTranslations while rendered inside the client TemplateDetailContent tree (500 at request time) — switched to the useTranslations hook. Was latent because demo pages were static-prerendered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,24 +2,43 @@ import type { Metadata } from "next";
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
import { TemplateDetailContent } from "@/components/templates/TemplateDetailContent";
|
import { TemplateDetailContent } from "@/components/templates/TemplateDetailContent";
|
||||||
import { VIDEO_TEMPLATES_CATALOG } from "@/lib/video-templates-catalog";
|
import { fetchProject } from "@/lib/admin-api";
|
||||||
|
import {
|
||||||
|
adminProjectToCatalogTemplate,
|
||||||
|
VIDEO_TEMPLATES_CATALOG,
|
||||||
|
type VideoCatalogTemplate,
|
||||||
|
} from "@/lib/video-templates-catalog";
|
||||||
|
|
||||||
interface TemplateDetailPageProps {
|
interface TemplateDetailPageProps {
|
||||||
params: { id: string };
|
params: Promise<{ id: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a template by its `[id]` route param — which is the container SLUG used
|
||||||
|
* in catalog links. Real (admin-managed) templates come from the content service
|
||||||
|
* via fetchProject(slug); the hardcoded demo catalog is the offline fallback.
|
||||||
|
*/
|
||||||
|
async function resolveTemplate(id: string): Promise<VideoCatalogTemplate | null> {
|
||||||
|
const admin = await fetchProject(id);
|
||||||
|
if (admin) return adminProjectToCatalogTemplate(admin);
|
||||||
|
return VIDEO_TEMPLATES_CATALOG.find((item) => item.id === id) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-render the demo catalog; real container slugs render on demand (ISR).
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return VIDEO_TEMPLATES_CATALOG.map((template) => ({ id: template.id }));
|
return VIDEO_TEMPLATES_CATALOG.map((template) => ({ id: template.id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateMetadata({ params }: TemplateDetailPageProps): Metadata {
|
export async function generateMetadata({ params }: TemplateDetailPageProps): Promise<Metadata> {
|
||||||
const template = VIDEO_TEMPLATES_CATALOG.find((item) => item.id === params.id);
|
const { id } = await params;
|
||||||
|
const template = await resolveTemplate(id);
|
||||||
if (!template) return {};
|
if (!template) return {};
|
||||||
return { title: `${template.name} — FlatRender` };
|
return { title: `${template.name} — FlatRender` };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TemplateDetailPage({ params }: TemplateDetailPageProps) {
|
export default async function TemplateDetailPage({ params }: TemplateDetailPageProps) {
|
||||||
const template = VIDEO_TEMPLATES_CATALOG.find((item) => item.id === params.id);
|
const { id } = await params;
|
||||||
|
const template = await resolveTemplate(id);
|
||||||
if (!template) notFound();
|
if (!template) notFound();
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-white">
|
<main className="min-h-screen bg-white">
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ChevronRight } from "lucide-react";
|
import { ChevronRight } from "lucide-react";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface TemplateDetailBreadcrumbProps {
|
interface TemplateDetailBreadcrumbProps {
|
||||||
templateName: string;
|
templateName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function TemplateDetailBreadcrumb({
|
export function TemplateDetailBreadcrumb({
|
||||||
templateName,
|
templateName,
|
||||||
}: TemplateDetailBreadcrumbProps) {
|
}: TemplateDetailBreadcrumbProps) {
|
||||||
const t = await getTranslations("auto.componentsTemplatesTemplateDetailBreadcrumb");
|
const t = useTranslations("auto.componentsTemplatesTemplateDetailBreadcrumb");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav aria-label={t("breadcrumbAriaLabel")} className="text-sm text-gray-500">
|
<nav aria-label={t("breadcrumbAriaLabel")} className="text-sm text-gray-500">
|
||||||
|
|||||||
Reference in New Issue
Block a user