feat(analytics): activate GA4 (G-KQ1JRV3KN7) + GDPR privacy page + GSC support
Client provided the GA4 Measurement ID and approved the standard policy. - Activate analytics: NEXT_PUBLIC_GA_ID set to the FLUX property G-KQ1JRV3KN7 in the env template, with the same value as the docker-compose build-arg fallback so it works out of the box on deploy. (GA Measurement IDs are public — they ship in page HTML — safe to commit.) - New GDPR-compliant Privacy & Cookie Policy page at /[locale]/privacy (all 5 locales), linked from the consent banner. Includes a clearly marked template disclaimer for legal review and a TODO on the contact email. Added to sitemap. - Consent banner now links via the locale-aware next-intl Link. - Google Search Console: optional NEXT_PUBLIC_GSC_VERIFICATION env var emits the google-site-verification meta tag (Dockerfile arg + docker-compose wired). Empty by default. Verified: build inlines G-KQ1JRV3KN7 into the client bundle; the 5 /privacy routes render; TypeScript clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,12 +79,17 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
apple: branding.appleTouchIconUrl,
|
||||
};
|
||||
|
||||
// Google Search Console verification (HTML-tag method). Emits
|
||||
// <meta name="google-site-verification" content="..."> when set.
|
||||
const gscToken = process.env.NEXT_PUBLIC_GSC_VERIFICATION;
|
||||
|
||||
return {
|
||||
metadataBase: new URL(APP_BASE_URL),
|
||||
title: "FLUX | Energy, Directed.",
|
||||
description: "Advanced Radio Frequency Solutions by Patrizio Grando.",
|
||||
icons,
|
||||
manifest: "/manifest.webmanifest",
|
||||
...(gscToken ? { verification: { google: gscToken } } : {}),
|
||||
openGraph: {
|
||||
title: "FLUX | Energy, Directed.",
|
||||
description: "Advanced Radio Frequency Solutions by Patrizio Grando.",
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { Metadata } from "next";
|
||||
import { setRequestLocale } from "next-intl/server";
|
||||
import { buildPageMetadata } from "@/lib/seo";
|
||||
import Breadcrumbs from "@/components/seo/Breadcrumbs";
|
||||
|
||||
// Static legal page. Revalidate rarely.
|
||||
export const revalidate = 86400;
|
||||
|
||||
const LAST_UPDATED = "June 2026";
|
||||
const COMPANY = "FLUX Srl";
|
||||
const ADDRESS = "Romano d'Ezzelino, Vicenza, Italy";
|
||||
const CONTACT_EMAIL = "privacy@rf-flux.com"; // TODO: confirm with FLUX legal
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
return buildPageMetadata({
|
||||
locale,
|
||||
pathWithoutLocale: "privacy",
|
||||
title: "Privacy & Cookie Policy | FLUX",
|
||||
description:
|
||||
"How FLUX Srl collects, uses and protects personal data on rf-flux.com, in compliance with the EU GDPR.",
|
||||
});
|
||||
}
|
||||
|
||||
export default async function PrivacyPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
const crumbs = [
|
||||
{ name: "Home", url: `/${locale}` },
|
||||
{ name: "Privacy & Cookie Policy", url: `/${locale}/privacy` },
|
||||
];
|
||||
|
||||
return (
|
||||
<main className="relative w-full min-h-screen bg-[#F5F5F7]">
|
||||
<div className="max-w-3xl mx-auto px-6 pt-28 md:pt-36 pb-24">
|
||||
<Breadcrumbs items={crumbs} />
|
||||
|
||||
<header className="mt-6 mb-10">
|
||||
<h1 className="text-3xl md:text-5xl font-light text-[#1D1D1F] tracking-tight">
|
||||
Privacy & Cookie <span className="font-medium">Policy</span>
|
||||
</h1>
|
||||
<p className="mt-3 text-sm text-[#86868B]">Last updated: {LAST_UPDATED}</p>
|
||||
</header>
|
||||
|
||||
{/* Template disclaimer — remove once reviewed by legal counsel */}
|
||||
<div className="mb-10 rounded-2xl border border-amber-300/50 bg-amber-50 p-4 text-sm text-amber-900">
|
||||
<strong>Template notice:</strong> this is a standard GDPR-compliant
|
||||
template provided as a starting point. Please have it reviewed and
|
||||
adapted by your legal counsel before relying on it, and confirm the
|
||||
contact details below.
|
||||
</div>
|
||||
|
||||
<div className="space-y-8 text-[#1D1D1F]">
|
||||
<Section title="1. Who we are">
|
||||
<P>
|
||||
{COMPANY} (“we”, “us”, “our”)
|
||||
is the data controller responsible for your personal data
|
||||
collected through this website, {SITE}. Our registered office is
|
||||
in {ADDRESS}.
|
||||
</P>
|
||||
<P>
|
||||
For any privacy-related request you can contact us at{" "}
|
||||
<a href={`mailto:${CONTACT_EMAIL}`} className="text-[#0066CC] underline underline-offset-2">
|
||||
{CONTACT_EMAIL}
|
||||
</a>
|
||||
.
|
||||
</P>
|
||||
</Section>
|
||||
|
||||
<Section title="2. What data we collect">
|
||||
<P>We collect personal data only when you actively provide it, or through privacy-respecting analytics:</P>
|
||||
<ul className="list-disc pl-5 space-y-1.5 text-[#3A3A3C]">
|
||||
<li>
|
||||
<strong>Contact & consultation requests:</strong> name, company,
|
||||
email, phone (optional) and any message you send through our
|
||||
forms or the FLUX AI assistant.
|
||||
</li>
|
||||
<li>
|
||||
<strong>AI assistant conversations:</strong> the messages you
|
||||
exchange with the on-site assistant, used to answer your
|
||||
questions and improve the service. Your IP address is stored
|
||||
only in pseudonymised (hashed) form.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Analytics:</strong> aggregated, anonymised usage data
|
||||
via Google Analytics 4 — but only after you accept analytics
|
||||
cookies (see section 4).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technical logs:</strong> standard server logs (IP,
|
||||
browser, timestamps) kept for security and troubleshooting.
|
||||
</li>
|
||||
</ul>
|
||||
</Section>
|
||||
|
||||
<Section title="3. How and why we use it">
|
||||
<P>We process your data on the following legal bases (GDPR Art. 6):</P>
|
||||
<ul className="list-disc pl-5 space-y-1.5 text-[#3A3A3C]">
|
||||
<li><strong>Consent</strong> — analytics cookies; you can withdraw it at any time.</li>
|
||||
<li><strong>Pre-contractual / legitimate interest</strong> — responding to your consultation and quote requests.</li>
|
||||
<li><strong>Legitimate interest</strong> — keeping the site secure and improving our products and content.</li>
|
||||
</ul>
|
||||
</Section>
|
||||
|
||||
<Section title="4. Cookies & consent">
|
||||
<P>
|
||||
We use a strictly necessary set of cookies to run the site and,
|
||||
optionally, analytics cookies. When you first visit, a banner lets
|
||||
you accept or decline analytics. We use Google Consent Mode v2:
|
||||
until you accept, no analytics cookies are set and no personal
|
||||
data is sent to Google. You can change your choice at any time by
|
||||
clearing the site cookies in your browser.
|
||||
</P>
|
||||
</Section>
|
||||
|
||||
<Section title="5. Who we share data with">
|
||||
<P>We never sell your data. We share it only with trusted processors strictly to operate the site:</P>
|
||||
<ul className="list-disc pl-5 space-y-1.5 text-[#3A3A3C]">
|
||||
<li><strong>Google (Analytics)</strong> — anonymised usage statistics, only with your consent.</li>
|
||||
<li><strong>Email / hosting providers</strong> — to deliver your requests to our team and host the site.</li>
|
||||
</ul>
|
||||
<P>
|
||||
Some providers may process data outside the EU/EEA; where that
|
||||
happens, transfers are covered by appropriate safeguards such as
|
||||
the EU Standard Contractual Clauses.
|
||||
</P>
|
||||
</Section>
|
||||
|
||||
<Section title="6. How long we keep it">
|
||||
<P>
|
||||
We keep consultation and contact data for as long as needed to
|
||||
handle your request and to comply with legal obligations, then
|
||||
delete or anonymise it. Analytics data is retained according to
|
||||
Google Analytics’ configured retention period.
|
||||
</P>
|
||||
</Section>
|
||||
|
||||
<Section title="7. Your rights">
|
||||
<P>Under the GDPR you have the right to:</P>
|
||||
<ul className="list-disc pl-5 space-y-1.5 text-[#3A3A3C]">
|
||||
<li>access, rectify or erase your personal data;</li>
|
||||
<li>restrict or object to processing;</li>
|
||||
<li>data portability;</li>
|
||||
<li>withdraw consent at any time;</li>
|
||||
<li>lodge a complaint with your data protection authority (in Italy, the Garante per la protezione dei dati personali).</li>
|
||||
</ul>
|
||||
<P>
|
||||
To exercise any of these rights, contact us at{" "}
|
||||
<a href={`mailto:${CONTACT_EMAIL}`} className="text-[#0066CC] underline underline-offset-2">
|
||||
{CONTACT_EMAIL}
|
||||
</a>
|
||||
.
|
||||
</P>
|
||||
</Section>
|
||||
|
||||
<Section title="8. Data security">
|
||||
<P>
|
||||
We apply appropriate technical and organisational measures
|
||||
(encryption in transit, access controls, pseudonymisation) to
|
||||
protect your data against unauthorised access, loss or misuse.
|
||||
</P>
|
||||
</Section>
|
||||
|
||||
<Section title="9. Changes to this policy">
|
||||
<P>
|
||||
We may update this policy from time to time. The “last
|
||||
updated” date at the top reflects the latest revision.
|
||||
</P>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const SITE = "rf-flux.com";
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<section>
|
||||
<h2 className="text-lg md:text-xl font-semibold text-[#1D1D1F] mb-3">{title}</h2>
|
||||
<div className="space-y-3 text-[15px] leading-relaxed">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function P({ children }: { children: React.ReactNode }) {
|
||||
return <p className="text-[#3A3A3C]">{children}</p>;
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
{ path: "/news", priority: 0.7, changeFrequency: "daily" as const },
|
||||
{ path: "/heritage", priority: 0.6, changeFrequency: "monthly" as const },
|
||||
{ path: "/team", priority: 0.6, changeFrequency: "monthly" as const },
|
||||
{ path: "/privacy", priority: 0.3, changeFrequency: "yearly" as const },
|
||||
];
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import {
|
||||
analyticsEnabled,
|
||||
readStoredConsent,
|
||||
@@ -59,12 +60,12 @@ export default function ConsentBanner() {
|
||||
<p className="text-sm font-medium text-[#1D1D1F]">{t("title")}</p>
|
||||
<p className="mt-1 text-xs leading-relaxed text-[#6E6E73]">
|
||||
{t("body")}{" "}
|
||||
<a
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="underline decoration-[#00B8CC] underline-offset-2 hover:text-[#1D1D1F]"
|
||||
>
|
||||
{t("learnMore")}
|
||||
</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user