Files
portifolio-evandrodevbr/app/cases/[slug]/page.tsx
T
Evandro Fonseca Junior e80f73d075 feat(cases): mídia no topo, títulos de busca curtos e CreativeWork
Case com mídia ao lado do título e play acessível; problema de cada cliente reescrito com base nos dados; serviços relacionados em 2 cards; FAQ antes do próximo passo e bloco 'Onde já apliquei isso'; Apolo citada em IoT e arquitetura. metaTitle até 43 caracteres e descrições até 155 com teste; JSON-LD CreativeWork; imagens de compartilhamento abaixo de 100 KB; 404 e robots corrigidos; server-only nos módulos com node:.
2026-10-14 14:15:25 -03:00

146 lines
5.0 KiB
TypeScript

import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { safeJsonLd } from "@/lib/jsonld";
import {
buildBreadcrumbList,
buildCaseJsonLd,
buildPageSocialMetadata,
} from "@/lib/seo";
import { caseStudies, isClientCase, siteUrl } from "@/lib/site-content";
import { CaseContactSection } from "../_components/CaseContactSection";
import { CaseHero } from "../_components/CaseHero";
import { CaseMediaSection } from "../_components/CaseMediaSection";
import { CaseResultSection } from "../_components/CaseResultSection";
interface CasePageProps {
params: Promise<{ slug: string }>;
}
export function generateStaticParams() {
return caseStudies.map((caseStudy) => ({ slug: caseStudy.id }));
}
export async function generateMetadata({
params,
}: CasePageProps): Promise<Metadata> {
const { slug } = await params;
const caseStudy = caseStudies.find((item) => item.id === slug);
if (!caseStudy) return { title: "Case não encontrado" };
const url = `${siteUrl}/cases/${caseStudy.id}`;
return {
title: caseStudy.metaTitle,
description: caseStudy.metaDescription,
...buildPageSocialMetadata({
url,
title: caseStudy.metaTitle,
description: caseStudy.metaDescription,
type: "article",
}),
};
}
export default async function CasePage({ params }: CasePageProps) {
const { slug } = await params;
const caseStudy = caseStudies.find((item) => item.id === slug);
if (!caseStudy) notFound();
const pageUrl = `${siteUrl}/cases/${caseStudy.id}`;
const isClient = isClientCase(caseStudy);
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
buildCaseJsonLd(caseStudy),
buildBreadcrumbList(pageUrl, [
["Início", siteUrl],
["Cases", `${siteUrl}/cases`],
[caseStudy.title, pageUrl],
] as const),
],
};
return (
<main>
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
<article>
<CaseHero caseStudy={caseStudy} isClient={isClient} />
<section className="ds-section" aria-labelledby="case-context">
<div className="ds-section-shell">
<div className="ds-grid-2 items-start">
<div className="max-w-[68ch]">
<h2 id="case-context" className="ds-card-title !mt-0">
{isClient
? "O cliente e o contexto"
: "O contexto do projeto"}
</h2>
<p className="ds-copy">{caseStudy.context}</p>
{caseStudy.role ? (
<p className="ds-copy">
<strong className="font-semibold text-[var(--ink)]">
Minha participação:
</strong>{" "}
{caseStudy.role}
</p>
) : null}
</div>
<div className="max-w-[68ch]">
<h2 id="case-problem" className="ds-card-title !mt-0">
O problema
</h2>
<p className="ds-copy">{caseStudy.problem}</p>
</div>
</div>
</div>
</section>
<section className="ds-section" aria-labelledby="case-done">
<div className="ds-section-shell">
<div className="max-w-[860px]">
<h2 id="case-done" className="ds-card-title !mt-0">
O que foi feito
</h2>
<ul className="ds-list space-y-sp-3 pl-[1.15rem]">
{caseStudy.deliveries.map((delivery) => (
<li key={delivery}>{delivery}</li>
))}
</ul>
<details className="mt-sp-8 border border-[var(--border)] bg-white/70 p-sp-5 md:p-sp-6">
<summary className="cursor-pointer py-sp-2 text-base font-semibold leading-relaxed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[var(--ink)]">
Detalhes técnicos
</summary>
<div className="max-w-[80ch]">
<p className="ds-kicker mt-sp-5">Tecnologias</p>
<ul
className="ds-tags !mt-sp-3 !pt-0"
aria-label="Tecnologias do projeto"
>
{caseStudy.stack.map((item) => (
<li key={item} className="ds-tag !whitespace-normal">
{item}
</li>
))}
</ul>
<p className="ds-kicker mt-sp-6">Decisões técnicas</p>
<ul className="ds-list !mt-sp-3 space-y-sp-2">
{caseStudy.decisions.map((decision) => (
<li key={decision}>{decision}</li>
))}
</ul>
</div>
</details>
</div>
</div>
</section>
<CaseMediaSection caseStudy={caseStudy} />
<CaseResultSection result={caseStudy.result} />
<CaseContactSection caseStudy={caseStudy} isClient={isClient} />
</article>
</main>
);
}