Cases da SEV Moda Íntima e da Lavação 047; case no formato cliente, problema, entrega, mídia e contato, com detalhes técnicos recolhidos; sem GitHub, ressalvas e fontes de commit. /cases separa clientes e Laboratório. Serviços mostram o case de cliente certo e o bloco de investimento e prazo só aparece preenchido. Metadata social própria por página, not-found com noindex, sitemap com /ia.
198 lines
6.9 KiB
TypeScript
198 lines
6.9 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import { Breadcrumb } from "@/app/components/Breadcrumb";
|
|
import {
|
|
pageTop,
|
|
primaryButton,
|
|
secondaryButtonDark,
|
|
} from "@/app/components/button-styles";
|
|
import WhatsAppLink from "@/app/components/WhatsAppLink";
|
|
import { safeJsonLd } from "@/lib/jsonld";
|
|
import { buildBreadcrumbList, buildPageSocialMetadata } from "@/lib/seo";
|
|
import { caseStudies, isClientCase, siteUrl } from "@/lib/site-content";
|
|
import { CaseMediaSection } from "../_components/CaseMediaSection";
|
|
import { CaseRelatedServices } from "../_components/CaseRelatedServices";
|
|
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}`;
|
|
const title = `${caseStudy.title}: ${caseStudy.subtitle}`;
|
|
return {
|
|
title,
|
|
description: caseStudy.summary,
|
|
...buildPageSocialMetadata({
|
|
url,
|
|
title,
|
|
description: caseStudy.summary,
|
|
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": [
|
|
buildBreadcrumbList(pageUrl, [
|
|
["Início", siteUrl],
|
|
["Cases", `${siteUrl}/cases`],
|
|
[caseStudy.title, pageUrl],
|
|
] as const),
|
|
],
|
|
};
|
|
|
|
return (
|
|
<main>
|
|
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
|
|
<article>
|
|
<section className={`ds-section ds-section-dark ${pageTop}`}>
|
|
<div className="ds-section-shell max-w-[960px]">
|
|
<Breadcrumb
|
|
items={[
|
|
{ label: "Início", href: "/" },
|
|
{ label: "Cases", href: "/cases" },
|
|
{ label: caseStudy.title, href: `/cases/${caseStudy.id}` },
|
|
]}
|
|
/>
|
|
<p className="ds-eyebrow">
|
|
{isClient ? "Case de cliente" : "Projeto de laboratório"} ·{" "}
|
|
{caseStudy.title}
|
|
</p>
|
|
<h1 className="ds-section-title text-balance text-white">
|
|
{caseStudy.headline}
|
|
</h1>
|
|
<p className="ds-copy max-w-[68ch] !text-[rgba(255,255,255,.78)]">
|
|
{caseStudy.summary}
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="ds-section" aria-labelledby="case-context">
|
|
<div className="ds-section-shell max-w-[960px]">
|
|
<div className="ds-grid-2 items-start">
|
|
<div>
|
|
<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>
|
|
<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 max-w-[960px]">
|
|
<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>
|
|
</div>
|
|
</section>
|
|
|
|
<CaseMediaSection caseStudy={caseStudy} />
|
|
|
|
<CaseResultSection result={caseStudy.result} />
|
|
|
|
<section
|
|
className="ds-section ds-section-dark"
|
|
aria-labelledby="case-contact"
|
|
>
|
|
<div className="ds-section-shell max-w-[960px]">
|
|
<h2 id="case-contact" className="ds-card-title !mt-0 text-white">
|
|
Quer um projeto parecido com este?
|
|
</h2>
|
|
<p className="ds-copy max-w-[60ch]">
|
|
Conte o que você precisa. A conversa começa direto no WhatsApp,
|
|
sem formulário.
|
|
</p>
|
|
<div className="mt-sp-6 flex flex-wrap items-center gap-sp-3">
|
|
<WhatsAppLink
|
|
service={`um projeto parecido com o da ${caseStudy.title}`}
|
|
placement="case_detail"
|
|
className={primaryButton}
|
|
>
|
|
Conversar sobre um projeto parecido
|
|
</WhatsAppLink>
|
|
<Link href="/cases" className={secondaryButtonDark}>
|
|
Ver outros cases
|
|
</Link>
|
|
</div>
|
|
<CaseRelatedServices caseId={caseStudy.id} />
|
|
</div>
|
|
</section>
|
|
|
|
<section className="ds-section" aria-label="Detalhes técnicos">
|
|
<div className="ds-section-shell max-w-[960px]">
|
|
<details className="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>
|
|
</section>
|
|
</article>
|
|
</main>
|
|
);
|
|
}
|