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 { 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 {
|
||||
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() {
|
||||
return VIDEO_TEMPLATES_CATALOG.map((template) => ({ id: template.id }));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: TemplateDetailPageProps): Metadata {
|
||||
const template = VIDEO_TEMPLATES_CATALOG.find((item) => item.id === params.id);
|
||||
export async function generateMetadata({ params }: TemplateDetailPageProps): Promise<Metadata> {
|
||||
const { id } = await params;
|
||||
const template = await resolveTemplate(id);
|
||||
if (!template) return {};
|
||||
return { title: `${template.name} — FlatRender` };
|
||||
}
|
||||
|
||||
export default function TemplateDetailPage({ params }: TemplateDetailPageProps) {
|
||||
const template = VIDEO_TEMPLATES_CATALOG.find((item) => item.id === params.id);
|
||||
export default async function TemplateDetailPage({ params }: TemplateDetailPageProps) {
|
||||
const { id } = await params;
|
||||
const template = await resolveTemplate(id);
|
||||
if (!template) notFound();
|
||||
return (
|
||||
<main className="min-h-screen bg-white">
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface TemplateDetailBreadcrumbProps {
|
||||
templateName: string;
|
||||
}
|
||||
|
||||
export async function TemplateDetailBreadcrumb({
|
||||
export function TemplateDetailBreadcrumb({
|
||||
templateName,
|
||||
}: TemplateDetailBreadcrumbProps) {
|
||||
const t = await getTranslations("auto.componentsTemplatesTemplateDetailBreadcrumb");
|
||||
const t = useTranslations("auto.componentsTemplatesTemplateDetailBreadcrumb");
|
||||
|
||||
return (
|
||||
<nav aria-label={t("breadcrumbAriaLabel")} className="text-sm text-gray-500">
|
||||
|
||||
Reference in New Issue
Block a user