Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Has been cancelled
Merged Directus and PostgreSQL project data, implemented single project fetch from CMS, and modernized the NotFound component with liquid design.
119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
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";
|
|
|
|
export const revalidate = 300;
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ locale: string; slug: string }>;
|
|
}): Promise<Metadata> {
|
|
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: any = 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<string, unknown>).markdown;
|
|
if (typeof markdown === "string") return markdown;
|
|
}
|
|
return dbProject.content;
|
|
})();
|
|
projectData = {
|
|
...rest,
|
|
title: tr?.title ?? dbProject.title,
|
|
description: tr?.description ?? dbProject.description,
|
|
content: localizedContent,
|
|
};
|
|
} else {
|
|
// Try Directus fallback
|
|
const directusProject = await getProjectBySlug(slug, locale);
|
|
if (directusProject) {
|
|
projectData = {
|
|
...directusProject,
|
|
id: parseInt(directusProject.id) || 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
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 (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
/>
|
|
<ProjectDetailClient project={projectData} locale={locale} />
|
|
</>
|
|
);
|
|
}
|
|
|