@graffico/legal
v0.3.7
Published
GDPR compliance solution for React/Next.js
Maintainers
Readme
@graffico/legal
CMP GDPR drop-in per React / Next.js. Banner + modale, policy server-rendered, tracker terzi che caricano solo dopo consenso, plugin Next.js per security headers.
pnpm add @graffico/legal1. Setup minimo
src/config/legal.ts:
import type { CompanyConfig } from "@graffico/legal";
export const legalConfig: CompanyConfig = {
name: "Mia Azienda",
domain: "azienda.it",
owners: "Mario Rossi",
addresses: "Via Roma 1, Milano",
vatNumbers: "01234567890",
email: "[email protected]",
policyVersion: "2026.05", // cambiarla invalida i consensi vecchi
services: ["google-analytics", "facebook-pixel", "contact-form"],
primaryColor: "#2c5282",
language: "it",
};app/layout.tsx:
import { LegalProvider } from "@graffico/legal/server";
import { legalConfig } from "@/config/legal";
export default function RootLayout({ children }) {
return (
<html>
<body>
<LegalProvider config={legalConfig}>{children}</LegalProvider>
</body>
</html>
);
}<LegalProvider> monta automaticamente banner + modale + floating toggle.
Tailwind / globals.css
La libreria usa Tailwind utility classes inline (niente CSS bundle). Devi dire al JIT di Tailwind di scansionare i file della libreria, altrimenti banner e modale appariranno senza stili.
Tailwind v4 — aggiungi una direttiva @source in src/app/globals.css (o equivalente):
@import "tailwindcss";
@source "../../node_modules/@graffico/legal/dist/**/*.{js,cjs}";Il path
../../parte dasrc/app/. Se il tuoglobals.csssta altrove, regola la profondità relativa. In monorepo con workspace, il dist è raggiungibile vianode_modulessimlink.
Tailwind v3 — aggiungi al tailwind.config.js:
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@graffico/legal/dist/**/*.{js,cjs}",
],
};2. Tracker — drop-in con consenso integrato
Importi il componente Legal, passi le API key. Lo script parte solo dopo consenso.
import {
ConsentModeBoot,
GoogleAnalyticsLegal,
GoogleAdsLegal,
MetaPixelLegal,
HotjarLegal,
GTMLegal,
EmailJSLegal,
} from "@graffico/legal";
<LegalProvider config={legalConfig}>
<ConsentModeBoot /> {/* Google Consent Mode v2 default = denied */}
<GoogleAnalyticsLegal gaId="G-XXXX" />
<GoogleAdsLegal
conversionId="AW-XXXX"
conversions={{ formSubmit: { sendTo: "AW-XXXX/abc", value: 1, currency: "EUR" } }}
/>
<MetaPixelLegal pixelId="123456" events={["Lead", "Contact"]} />
<HotjarLegal siteId={1234567} />
<EmailJSLegal
serviceId={process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID!}
templateId={process.env.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID!}
publicKey={process.env.NEXT_PUBLIC_EMAILJS_USER_ID!}
/>
{children}
</LegalProvider>| Componente | Categoria default | Script gated | Note |
|---|---|---|---|
| ConsentModeBoot | — | — | Boot gtag stub + Consent Mode v2. Sempre per primo. |
| GoogleAnalyticsLegal | statistical | gtag/js?id=G-… | Anonymize IP on di default. |
| GoogleAdsLegal | marketing | gtag/js?id=AW-… | Mappa conversioni dichiarativa. |
| MetaPixelLegal | marketing | fbevents.js | Whitelist events opzionale. Su revoca → fbq('consent','revoke'). |
| HotjarLegal | statistical | static.hotjar.com/c/hotjar-… | Session recording + heatmap. |
| GTMLegal | marketing | gtm.js?id=GTM-… | Lascia ai tag interni la granularità. |
| EmailJSLegal | — (art. 6.1.b) | — | Config holder per useEmailJS(). |
3. Hook trigger eventi
Nei tuoi componenti "use client":
import { useGAEvent, useMetaEvent, useAdsConversion, useHotjarEvent } from "@graffico/legal";
const trackGA = useGAEvent();
const trackLead = useMetaEvent("Lead");
const fireConversion = useAdsConversion("formSubmit");
const trackHj = useHotjarEvent();
// Tutti diventano no-op senza consenso — chiama liberamente
trackGA("button_click", { id: "hero" });
trackLead({ value: 99, currency: "EUR" });
fireConversion();
trackHj("scroll_to_pricing");4. Form contatto con consenso
"use client";
import { useEmailJS, ConsentCheckbox } from "@graffico/legal";
export function ContactForm() {
const { send, status } = useEmailJS();
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const res = await send(e.currentTarget);
if (!res.ok) console.warn(res.error); // CONSENT_MISSING | CONFIG_MISSING | SEND_FAILED
}
return (
<form onSubmit={onSubmit}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<ConsentCheckbox /> {/* obbligatorio: blocca submit se non spuntato */}
<button disabled={status === "loading"}>Invia</button>
</form>
);
}send() inietta automaticamente nel payload mail: _consentId, _consentTimestamp, _policyVersion (prova legale art. 7.1).
5. Pagine policy
Auto-renderizzate dalla config — incluse sezioni Processors / Transfers / Retention / DPO se valorizzati.
// app/privacy-policy/page.tsx
import { PrivacyPolicy } from "@graffico/legal/server";
import { legalConfig } from "@/config/legal";
export default () => <PrivacyPolicy config={legalConfig} />;
// app/cookie-policy/page.tsx
import { CookiePolicy } from "@graffico/legal/server";
export default () => <CookiePolicy config={legalConfig} />;6. Gate per embed terzi
import { ConsentGate, useConsentCategory } from "@graffico/legal";
<ConsentGate
category="marketing"
fallback={<p>Abilita cookie marketing per vedere il video.</p>}
>
<iframe src="https://youtube.com/embed/…" />
</ConsentGate>7. Next.js plugin — security headers
CSP derivata dai services[] + processors[]. HSTS, X-Frame, Permissions-Policy, COOP, Referrer-Policy.
// next.config.ts
import { withLegal } from "@graffico/legal/next";
import { legalConfig } from "@/config/legal";
const nextConfig = { /* … */ };
export default withLegal(nextConfig, legalConfig, {
cspReportOnly: process.env.CSP_ENFORCE !== "1", // rollout: 7gg report-only → enforce
});Per netlify.toml:
import { emitNetlifyHeaders } from "@graffico/legal/next";
console.log(emitNetlifyHeaders(legalConfig));8. CompanyConfig
interface CompanyConfig {
name: string;
domain: string;
owners: string;
addresses: string | string[];
vatNumbers: string | string[];
email: string;
policyVersion?: string; // cambiarla invalida i consensi pregressi
lastUpdated?: string; // ISO date, default oggi
services?: ServiceReference[]; // tracker dichiarati (preset id o oggetto)
processors?: Processor[]; // destinatari/responsabili esterni
retention?: RetentionMap; // tempi di conservazione per categoria
dpo?: DpoInfo; // se nominato (art. 37 GDPR)
theme?: "light" | "dark";
primaryColor?: string;
language?: "it" | "en";
togglePosition?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
}
interface Processor {
name: string;
purpose: string;
country: string;
transferBasis?: string; // "DPF + SCC 2021/914", "SCC", …
privacyUrl?: string;
}
interface RetentionMap {
contactForms?: string; // "90d"
analytics?: string; // "14m"
marketing?: string; // "2y"
[k: string]: string | undefined;
}
interface DpoInfo {
name: string;
email: string;
phone?: string;
}9. Conformità GDPR / ePrivacy — cosa fa già la lib
- TTL consenso 180 giorni (
CONSENT_TTL_MS), allineato al Garante 10/06/2021. Scaduto → re-prompt. - Re-prompt automatico al cambio
policyVersion. - Auto-decline su GPC / DNT (
navigator.globalPrivacyControl,Sec-GPC) senza UI. - Prova legale:
consentIdUUID +timestampISO +policyVersion+sourcein localStorage e nel payload mail. - Parità grafica reject/accept nel banner (Garante 10/06/2021 §4).
- Categorie indipendenti:
necessary/statistical/marketing. Default OFF. - Niente cookie wall: il sito resta fruibile senza consenso.
- Zero telemetria della libreria stessa.
10. Service preset (id stringa per services[])
| Categoria | Id |
|---|---|
| Analytics | google-analytics, microsoft-clarity, hotjar |
| Marketing | facebook-pixel, google-ads, linkedin-insight, tiktok-pixel |
| Tag manager | google-tag-manager |
| Pagamenti | stripe, paypal |
| Contenuti | youtube, google-maps, google-fonts |
| Form / mail | contact-form, mailchimp, newsletter |
Servizio inline custom:
{
id: "crm",
name: "CRM Interno",
category: "provided", // "automatic" | "provided" | "marketing"
purposes: ["Gestione clienti"],
dataCollected: ["Email", "Nome"],
}11. API export
@graffico/legal/server — LegalProvider, PrivacyPolicy, CookiePolicy.
@graffico/legal (client) —
- Tracker:
ConsentModeBoot,GoogleAnalyticsLegal,GoogleAdsLegal,MetaPixelLegal,HotjarLegal,GTMLegal,EmailJSLegal - Hook:
useConsent,useConsentCategory,useIsHydrated,useGAEvent,useAdsConversion,useMetaEvent,useHotjarEvent,useHotjarIdentify,useEmailJS,useT,useLegalLanguage - UI:
ConsentBanner,PreferenceModal,FloatingConsentToggle,ConsentGate,ConsentCheckbox,ServiceGrid - Azioni:
acceptAll,declineAll,savePreferences,resetConsent,setModalOpen,setLanguage - Primitive:
subscribeConsent,getConsentSnapshot,CONSENT_TTL_MS,SERVICE_PRESETS
@graffico/legal/next — withLegal, emitNetlifyHeaders, buildLegalHeaders, buildCspFromConfig.
Requisiti
- React ≥ 18 (App Router consigliato)
- Tailwind opzionale (la UI usa classi Tailwind)
@emailjs/browser≥ 4 — peer optional, solo se usiEmailJSLegal
Made by Graffico Studio.
