feat(servicos): topo, entregáveis e cases por serviço, assunto próprio no WhatsApp e títulos curtos
This commit is contained in:
1 parent
cf360041bd
commit
2fd8b063d9
33 files changed
+1578
-300
No files matched your search
@@ -1,13 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getCaseMedia } from "@/app/cases/_components/case-media";
|
||||
import {
|
||||
compactHero,
|
||||
heroWithMediaGrid,
|
||||
} from "@/app/cases/_components/hero-styles";
|
||||
import { Breadcrumb } from "@/app/components/Breadcrumb";
|
||||
import {
|
||||
pageTop,
|
||||
primaryButton,
|
||||
secondaryButtonDark,
|
||||
} from "@/app/components/button-styles";
|
||||
@@ -22,16 +15,20 @@ import {
|
||||
} from "@/lib/seo";
|
||||
import {
|
||||
buttonLabels,
|
||||
caseStudies,
|
||||
getRelatedCases,
|
||||
getServiceOffer,
|
||||
getServicePage,
|
||||
servicePages,
|
||||
siteUrl,
|
||||
} from "@/lib/site-content";
|
||||
import { ServiceDiagram } from "../_components/ServiceDiagram";
|
||||
import { ServiceHeroCover } from "../_components/ServiceHeroCover";
|
||||
import { ServiceAlsoCases } from "../_components/ServiceAlsoCases";
|
||||
import { ServiceDeliverables } from "../_components/ServiceDeliverables";
|
||||
import { ServiceHero } from "../_components/ServiceHero";
|
||||
import { ServiceOffer } from "../_components/ServiceOffer";
|
||||
import { ServiceRelatedCases } from "../_components/ServiceRelatedCases";
|
||||
import {
|
||||
resolveServiceHero,
|
||||
splitServiceCases,
|
||||
} from "../_components/service-page";
|
||||
|
||||
interface ServicePageProps {
|
||||
params: Promise<{
|
||||
@@ -77,18 +74,21 @@ export default async function ServicePage({ params }: ServicePageProps) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const relatedCases = getRelatedCases(service.relatedCaseIds);
|
||||
// capa do topo: o case escolhido para o serviço, se tiver captura gravada do aparelho pedido; senão, o esquema de fluxo
|
||||
const heroDevice = service.heroCase?.device ?? "desktop";
|
||||
const heroCase = caseStudies.find(
|
||||
({ id }) =>
|
||||
id === service.heroCase?.id && Boolean(getCaseMedia(id, heroDevice)),
|
||||
);
|
||||
const diagram = heroCase ? undefined : service.diagram;
|
||||
const hasHeroSide = Boolean(heroCase || diagram);
|
||||
// o case da capa é citado pela legenda dela, e não ganha cartão em "Onde já apliquei isso"
|
||||
const hasCaseCards = relatedCases.some(({ id }) => id !== heroCase?.id);
|
||||
const hero = resolveServiceHero(service);
|
||||
const { cards, also } = splitServiceCases(service);
|
||||
// as duas primeiras ofertas têm página aqui; "O que você recebe" e "Primeiro passo" vêm de serviceOffers, como na home
|
||||
const offer = getServiceOffer(service.slug);
|
||||
const pageUrl = `${siteUrl}/servicos/${service.slug}`;
|
||||
const audienceCard = (
|
||||
<article className="ds-card">
|
||||
<h2 className="ds-card-title !mt-0">Para quem faz sentido</h2>
|
||||
<ul className="ds-list space-y-sp-2">
|
||||
{service.audience.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
);
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
@@ -106,90 +106,44 @@ export default async function ServicePage({ params }: ServicePageProps) {
|
||||
<main>
|
||||
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
|
||||
|
||||
<section
|
||||
className={`ds-section ds-section-dark ${pageTop} ${
|
||||
hasHeroSide ? "" : compactHero
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`ds-section-shell ${hasHeroSide ? heroWithMediaGrid : ""}`}
|
||||
>
|
||||
<div className="max-w-[860px]">
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ label: "Início", href: "/" },
|
||||
{ label: "Serviços", href: "/servicos" },
|
||||
{ label: service.navLabel, href: `/servicos/${service.slug}` },
|
||||
]}
|
||||
/>
|
||||
<p className="ds-eyebrow">{service.eyebrow}</p>
|
||||
<h1 className="ds-section-title max-w-[22ch] text-balance text-white">
|
||||
{service.headline}
|
||||
</h1>
|
||||
<p className="ds-copy max-w-[64ch] !text-[rgba(255,255,255,0.78)]">
|
||||
{service.answer}
|
||||
</p>
|
||||
<div className="mt-sp-8 flex flex-wrap gap-sp-3">
|
||||
<WhatsAppLink
|
||||
service={service.navLabel}
|
||||
placement="service_detail_hero"
|
||||
className={primaryButton}
|
||||
>
|
||||
{buttonLabels.contact}
|
||||
</WhatsAppLink>
|
||||
{hasCaseCards ? (
|
||||
<a href="#casos-relacionados" className={secondaryButtonDark}>
|
||||
Ver projetos parecidos
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="mt-sp-4 text-[15px] leading-6 text-white/70">
|
||||
O botão abre uma conversa no WhatsApp, sem formulário. Garuva e
|
||||
Joinville (SC) · atendimento remoto em todo o Brasil.
|
||||
</p>
|
||||
</div>
|
||||
{diagram ? (
|
||||
<ServiceDiagram kind={diagram} />
|
||||
) : (
|
||||
<ServiceHeroCover
|
||||
caseStudy={heroCase}
|
||||
device={heroDevice}
|
||||
relevance={
|
||||
heroCase ? service.caseRelevance[heroCase.id] : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<ServiceHero service={service} hero={hero} />
|
||||
|
||||
<ServiceRelatedCases
|
||||
cases={relatedCases}
|
||||
cases={cards}
|
||||
relevance={service.caseRelevance}
|
||||
experienceNote={service.experienceNote}
|
||||
heroCaseId={heroCase?.id}
|
||||
heroCaseId={hero.heroCase?.id}
|
||||
/>
|
||||
|
||||
{also ? (
|
||||
<ServiceAlsoCases
|
||||
title={also.title}
|
||||
cases={also.cases}
|
||||
relevance={service.caseRelevance}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{offer ? <ServiceDeliverables offer={offer} /> : null}
|
||||
|
||||
<section className="ds-section">
|
||||
<div className="ds-section-shell">
|
||||
<div className="ds-grid-2">
|
||||
<article className="ds-card">
|
||||
<h2 className="ds-card-title !mt-0">Para quem faz sentido</h2>
|
||||
<ul className="ds-list space-y-sp-2">
|
||||
{service.audience.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
{offer ? (
|
||||
// a página da oferta já tem "O que você recebe": uma lista de entregáveis só, sem "O que podemos entregar"
|
||||
audienceCard
|
||||
) : (
|
||||
<div className="ds-grid-2">
|
||||
{audienceCard}
|
||||
|
||||
<article className="ds-card">
|
||||
<h2 className="ds-card-title !mt-0">O que podemos entregar</h2>
|
||||
<ul className="ds-list space-y-sp-2">
|
||||
{service.outcomes.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
<article className="ds-card">
|
||||
<h2 className="ds-card-title !mt-0">O que podemos entregar</h2>
|
||||
<ul className="ds-list space-y-sp-2">
|
||||
{service.outcomes.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -223,7 +177,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ds-section-shell mt-sp-8">
|
||||
<div className="ds-section-shell !mt-sp-8">
|
||||
<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 e tecnologias
|
||||
@@ -268,7 +222,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
|
||||
</p>
|
||||
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
|
||||
<WhatsAppLink
|
||||
service={service.navLabel}
|
||||
service={service.whatsappSubject}
|
||||
placement="service_detail_footer"
|
||||
className={primaryButton}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user