feat: publish privacy and consent links

This commit is contained in:
Evandro Fonseca Junior committed 2026-09-04 20:04:27 -03:00
1 parent a9a35cf936
commit f83c5e1ada
9 files changed
+254 -1

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
import { ImageResponse } from "next/og";
import { OG_CONTENT_TYPE, OG_SIZE, OgCard } from "@/lib/og-card";
export const alt = "Privacidade e consentimento no Evandro.dev.br";
export const size = OG_SIZE;
export const contentType = OG_CONTENT_TYPE;
export default function Image() {
return new ImageResponse(
<OgCard
eyebrow="Privacidade"
title="Privacidade e consentimento"
description="Informações factuais sobre contato, analytics e preferências no portfólio."
/>,
OG_SIZE,
);
}
+57
View File
@@ -0,0 +1,57 @@
import { render, screen, within } from "@testing-library/react";
import PrivacyPage, { metadata } from "./page";
it("publishes factual privacy content and a breadcrumb-only JSON-LD graph", () => {
const { container } = render(<PrivacyPage />);
expect(
screen.getByRole("heading", { level: 1, name: /privacidade/i }),
).toBeVisible();
expect(screen.getByText(/evandro\.analytics-consent\.v1/)).toBeVisible();
expect(screen.getAllByText(/WhatsApp/).length).toBeGreaterThan(0);
expect(screen.getByText(/nenhuma tag do Google é carregada/i)).toBeVisible();
expect(screen.getByText(/2026-09-04/)).toBeVisible();
const breadcrumb = screen.getByRole("navigation", { name: "Breadcrumb" });
const breadcrumbLinks = within(breadcrumb).getAllByRole("link");
expect(breadcrumbLinks.map((link) => link.textContent)).toEqual([
"Início",
"Privacidade",
]);
expect(breadcrumbLinks[0]).toHaveAttribute("href", "/");
expect(breadcrumbLinks[1]).toHaveAttribute("href", "/privacidade");
expect(breadcrumbLinks[1]).toHaveAttribute("aria-current", "page");
const scripts = container.querySelectorAll<HTMLScriptElement>(
'script[type="application/ld+json"]',
);
expect(scripts).toHaveLength(1);
const jsonLd = JSON.parse(scripts[0]?.textContent ?? "{}");
expect(jsonLd["@graph"]).toHaveLength(1);
expect(jsonLd["@graph"][0]["@type"]).toBe("BreadcrumbList");
expect(JSON.stringify(jsonLd)).not.toMatch(
/Person|Organization|LocalBusiness|ProfessionalService|Service/,
);
expect(jsonLd["@graph"][0].itemListElement).toEqual([
{
"@type": "ListItem",
position: 1,
name: "Início",
item: "https://evandro.dev.br",
},
{
"@type": "ListItem",
position: 2,
name: "Privacidade",
item: "https://evandro.dev.br/privacidade",
},
]);
});
it("uses privacy-specific canonical metadata", () => {
expect(metadata).toMatchObject({
title: expect.stringMatching(/privacidade/i),
description: expect.any(String),
alternates: { canonical: "https://evandro.dev.br/privacidade" },
});
});
+151
View File
@@ -0,0 +1,151 @@
import type { Metadata } from "next";
import Link from "next/link";
import { safeJsonLd } from "@/lib/jsonld";
import { buildBreadcrumbList } from "@/lib/seo";
import { siteName, siteUrl } from "@/lib/site-content";
const pageUrl = `${siteUrl}/privacidade`;
export const metadata: Metadata = {
title: "Privacidade e consentimento",
description:
"Como o portfólio trata dados de contato, consentimento de analytics, links externos e solicitações de privacidade.",
alternates: { canonical: pageUrl },
openGraph: {
title: `Privacidade e consentimento | ${siteName}`,
description:
"Informações factuais sobre contato, analytics e preferências de privacidade no portfólio.",
url: pageUrl,
siteName,
type: "website",
},
};
export default function PrivacyPage() {
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
buildBreadcrumbList(pageUrl, [
["Início", siteUrl],
["Privacidade", pageUrl],
] as const),
],
};
return (
<main>
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
<section className="ds-section ds-section-dark">
<div className="ds-section-shell max-w-[960px]">
<nav aria-label="Breadcrumb" className="mb-sp-6">
<ol className="flex flex-wrap items-center gap-sp-2 text-sm">
<li>
<Link className="ds-link ds-link-muted" href="/">
Início
</Link>
</li>
<li className="flex items-center gap-sp-2">
<span aria-hidden="true">/</span>
<Link
aria-current="page"
className="ds-link ds-link-muted"
href="/privacidade"
>
Privacidade
</Link>
</li>
</ol>
</nav>
<p className="ds-eyebrow">Privacidade e consentimento</p>
<h1 className="ds-section-title text-white">
Privacidade e consentimento
</h1>
<p className="ds-copy max-w-[70ch] !text-[rgba(255,255,255,.78)]">
Esta página explica, de forma objetiva, como funcionam o contato e
as escolhas de analytics neste portfólio.
</p>
</div>
</section>
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
<article className="ds-card">
<p className="ds-kicker">Responsável e contato</p>
<h2 className="ds-card-title">Evandro Fonseca Junior</h2>
<p className="ds-copy">
O responsável pelo site é Evandro Fonseca Junior. Para contato
ou solicitações relacionadas a esta página, escreva para{" "}
<a
className="ds-link ds-link-accent"
href="mailto:[email protected]"
>
evandrofonsecajunior@gmail.com
</a>
.
</p>
</article>
<article className="ds-card">
<p className="ds-kicker">Formulário de contato</p>
<h2 className="ds-card-title">Mensagem pelo WhatsApp</h2>
<p className="ds-copy">
Os dados digitados no formulário são transformados em uma
mensagem e abrem o WhatsApp. O site não possui endpoint próprio
de armazenamento desse formulário.
</p>
</article>
<article className="ds-card">
<p className="ds-kicker">Analytics</p>
<h2 className="ds-card-title">Escolha antes da medição</h2>
<p className="ds-copy">
Antes de <code>granted</code>, nenhuma tag do Google é
carregada. A escolha fica no <code>localStorage</code> sob a
chave <code>evandro.analytics-consent.v1</code>, com os valores{" "}
<code>granted</code> ou <code>denied</code>.
</p>
<p className="ds-copy mt-sp-4">
A publicidade permanece negada. Ao conceder analytics, o Google
pode criar cookies <code>_ga*</code>. Ao revogar, novas coletas
são desativadas e os cookies de analytics acessíveis são
expirados.
</p>
</article>
<article className="ds-card">
<p className="ds-kicker">Preferências e links</p>
<h2 className="ds-card-title">Você pode mudar a escolha</h2>
<p className="ds-copy">
Para alterar a preferência, use o controle existente{" "}
<strong>“Preferências de analytics”</strong> para acessar suas
preferências de privacidade. Links externos para GitHub,
LinkedIn, Instagram, demos e WhatsApp seguem as políticas
próprias de cada serviço.
</p>
<p className="ds-copy mt-sp-4">
Solicitações relacionadas aos seus direitos devem ser
encaminhadas ao e-mail publicado acima.
</p>
<Link
className="ds-link ds-link-accent mt-sp-5 inline-block"
href="/"
>
Voltar ao início
</Link>
</article>
</div>
<div className="mt-sp-8 border-t border-[var(--border)] pt-sp-5">
<p className="ds-kicker">Vigência</p>
<p className="ds-copy mt-sp-2">
Esta versão está vigente desde{" "}
<time dateTime="2026-09-04">2026-09-04</time>.
</p>
</div>
</div>
</section>
</main>
);
}