import { prisma } from "@/lib/prisma"; import ProjectDetailClient from "@/app/_ui/ProjectDetailClient"; import { notFound } from "next/navigation"; import type { Metadata } from "next"; import { getLanguageAlternates, toAbsoluteUrl } from "@/lib/seo"; import { getProjectBySlug } from "@/lib/directus"; import { ProjectDetailData } from "@/app/_ui/ProjectDetailClient"; export const revalidate = 300; export async function generateMetadata({ params, }: { params: Promise<{ locale: string; slug: string }>; }): Promise { const { locale, slug } = await params; // Try Directus first for metadata const directusProject = await getProjectBySlug(slug, locale); if (directusProject) { return { title: directusProject.title, description: directusProject.description, alternates: { canonical: toAbsoluteUrl(`/${locale}/projects/${slug}`), languages: getLanguageAlternates({ pathWithoutLocale: `projects/${slug}` }), }, }; } const languages = getLanguageAlternates({ pathWithoutLocale: `projects/${slug}` }); return { alternates: { canonical: toAbsoluteUrl(`/${locale}/projects/${slug}`), languages, }, }; } export default async function ProjectPage({ params, }: { params: Promise<{ locale: string; slug: string }>; }) { const { locale, slug } = await params; // Try PostgreSQL first const dbProject = await prisma.project.findFirst({ where: { slug, published: true }, include: { translations: { select: { title: true, description: true, content: true, locale: true }, }, }, }); let projectData: ProjectDetailData | null = null; if (dbProject) { const trPreferred = dbProject.translations?.find((t) => t.locale === locale && (t?.title || t?.description)); const trDefault = dbProject.translations?.find( (t) => t.locale === dbProject.defaultLocale && (t?.title || t?.description), ); const tr = trPreferred ?? trDefault; const { translations: _translations, ...rest } = dbProject; const localizedContent = (() => { if (typeof tr?.content === "string") return tr.content; if (tr?.content && typeof tr.content === "object" && "markdown" in tr.content) { const markdown = (tr.content as Record).markdown; if (typeof markdown === "string") return markdown; } return dbProject.content; })(); projectData = { ...rest, title: tr?.title ?? dbProject.title, description: tr?.description ?? dbProject.description, content: localizedContent, } as ProjectDetailData; } else { // Try Directus fallback const directusProject = await getProjectBySlug(slug, locale); if (directusProject) { projectData = { ...directusProject, id: typeof directusProject.id === 'string' ? (parseInt(directusProject.id) || 0) : directusProject.id, } as ProjectDetailData; } } if (!projectData) return notFound(); const jsonLd = { "@context": "https://schema.org", "@type": "SoftwareSourceCode", "name": projectData.title, "description": projectData.description, "codeRepository": projectData.github_url || projectData.github, "programmingLanguage": projectData.technologies, "author": { "@type": "Person", "name": "Dennis Konkol" }, "dateCreated": projectData.date || projectData.created_at, "url": toAbsoluteUrl(`/${locale}/projects/${slug}`), "image": (projectData.imageUrl || projectData.image_url) ? toAbsoluteUrl((projectData.imageUrl || projectData.image_url)!) : undefined, }; return ( <>