diff --git a/Dockerfile b/Dockerfile index 5ed2d50..0052207 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,6 +51,8 @@ ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" # docker-compose build.args -> .env. Empty by default = analytics disabled. ARG NEXT_PUBLIC_GA_ID="" ENV NEXT_PUBLIC_GA_ID=$NEXT_PUBLIC_GA_ID +ARG NEXT_PUBLIC_GSC_VERIFICATION="" +ENV NEXT_PUBLIC_GSC_VERIFICATION=$NEXT_PUBLIC_GSC_VERIFICATION RUN npm run build diff --git a/docker-compose.yml b/docker-compose.yml index 5364b5a..64195f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,9 +43,11 @@ services: context: . dockerfile: Dockerfile args: - # NEXT_PUBLIC_GA_ID must be available at build time (Next.js inlines - # NEXT_PUBLIC_* into the client bundle). Sourced from .env on the host. - NEXT_PUBLIC_GA_ID: ${NEXT_PUBLIC_GA_ID:-} + # NEXT_PUBLIC_* are inlined into the client bundle at build time. + # Sourced from .env on the host; the fallback is the FLUX GA4 ID so + # analytics works out of the box even if .env doesn't override it. + NEXT_PUBLIC_GA_ID: ${NEXT_PUBLIC_GA_ID:-G-KQ1JRV3KN7} + NEXT_PUBLIC_GSC_VERIFICATION: ${NEXT_PUBLIC_GSC_VERIFICATION:-} restart: always depends_on: postgres: diff --git a/env b/env index 011e41f..a9871d0 100644 --- a/env +++ b/env @@ -19,7 +19,12 @@ SESSION_SECRET="CHANGE_ME_openssl_rand_base64_48_min_32_chars" # Google Analytics 4 Measurement ID (format: G-XXXXXXXXXX). # Leave empty to disable analytics entirely — the site loads no Google # scripts and the consent banner stays hidden until this is set. -NEXT_PUBLIC_GA_ID="" +# This is a PUBLIC value (it ships in the page HTML), safe to commit. +NEXT_PUBLIC_GA_ID="G-KQ1JRV3KN7" + +# Google Search Console verification token (the content="" value from the +# HTML-tag verification method). Leave empty if you verify via DNS or GA. +NEXT_PUBLIC_GSC_VERIFICATION="" # OPEN AI KEY OPENAI_API_KEY=sk-proj-dsdsdsds-kaDAHhZXPYsK6-4uw0UWJZ0YuLnQfVoEA diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 182c1e5..7eb7c61 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -79,12 +79,17 @@ export async function generateMetadata(): Promise { apple: branding.appleTouchIconUrl, }; + // Google Search Console verification (HTML-tag method). Emits + // 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.", diff --git a/src/app/[locale]/privacy/page.tsx b/src/app/[locale]/privacy/page.tsx new file mode 100644 index 0000000..8c53692 --- /dev/null +++ b/src/app/[locale]/privacy/page.tsx @@ -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 { + 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 ( +
+
+ + +
+

+ Privacy & Cookie Policy +

+

Last updated: {LAST_UPDATED}

+
+ + {/* Template disclaimer — remove once reviewed by legal counsel */} +
+ Template notice: 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. +
+ +
+
+

+ {COMPANY} (“we”, “us”, “our”) + is the data controller responsible for your personal data + collected through this website, {SITE}. Our registered office is + in {ADDRESS}. +

+

+ For any privacy-related request you can contact us at{" "} + + {CONTACT_EMAIL} + + . +

+
+ +
+

We collect personal data only when you actively provide it, or through privacy-respecting analytics:

+
    +
  • + Contact & consultation requests: name, company, + email, phone (optional) and any message you send through our + forms or the FLUX AI assistant. +
  • +
  • + AI assistant conversations: 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. +
  • +
  • + Analytics: aggregated, anonymised usage data + via Google Analytics 4 — but only after you accept analytics + cookies (see section 4). +
  • +
  • + Technical logs: standard server logs (IP, + browser, timestamps) kept for security and troubleshooting. +
  • +
+
+ +
+

We process your data on the following legal bases (GDPR Art. 6):

+
    +
  • Consent — analytics cookies; you can withdraw it at any time.
  • +
  • Pre-contractual / legitimate interest — responding to your consultation and quote requests.
  • +
  • Legitimate interest — keeping the site secure and improving our products and content.
  • +
+
+ +
+

+ 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. +

+
+ +
+

We never sell your data. We share it only with trusted processors strictly to operate the site:

+
    +
  • Google (Analytics) — anonymised usage statistics, only with your consent.
  • +
  • Email / hosting providers — to deliver your requests to our team and host the site.
  • +
+

+ 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. +

+
+ +
+

+ 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. +

+
+ +
+

Under the GDPR you have the right to:

+
    +
  • access, rectify or erase your personal data;
  • +
  • restrict or object to processing;
  • +
  • data portability;
  • +
  • withdraw consent at any time;
  • +
  • lodge a complaint with your data protection authority (in Italy, the Garante per la protezione dei dati personali).
  • +
+

+ To exercise any of these rights, contact us at{" "} + + {CONTACT_EMAIL} + + . +

+
+ +
+

+ We apply appropriate technical and organisational measures + (encryption in transit, access controls, pseudonymisation) to + protect your data against unauthorised access, loss or misuse. +

+
+ +
+

+ We may update this policy from time to time. The “last + updated” date at the top reflects the latest revision. +

+
+
+
+
+ ); +} + +const SITE = "rf-flux.com"; + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function P({ children }: { children: React.ReactNode }) { + return

{children}

; +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index bfdb4c9..26354f3 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -30,6 +30,7 @@ export default async function sitemap(): Promise { { 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) { diff --git a/src/components/analytics/ConsentBanner.tsx b/src/components/analytics/ConsentBanner.tsx index 121b9e8..135c87f 100644 --- a/src/components/analytics/ConsentBanner.tsx +++ b/src/components/analytics/ConsentBanner.tsx @@ -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() {

{t("title")}

{t("body")}{" "} - {t("learnMore")} - +