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:.
146 lines
5.0 KiB
TypeScript
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>
|
|
);
|
|
}
|