Nowa podstrona produktowa zbudowana wg wzorca PrivacyPolicyPage: własny
komponent, własna ścieżka w App.tsx, header z linkiem powrotu, wspólna stopka.
Bez i18n — treść po polsku, spójnie z resztą serwisu poza contact.*/privacy.*.
Sekcje "Dla domu" / "Dla firm" jako dwie karty obok siebie (dwie ścieżki,
jeden rdzeń). CTA na dole prowadzi do /#contact.
Podlinkowania:
- karta oferty "Multiagent Systems & Infrastructure" dostaje link
"Zobacz jak to działa" → /platforma (opcjonalne pole `link` w offerings)
- nawigacja "Platforma" w Header.tsx i Footer.tsx przepięta z #process
na /platforma
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- nginx.conf: SPA fallback (try_files → index.html), bez niego /privacy zwracało 404
- Dockerfile: kopiuje nginx.conf do kontenera
- i18n/pl.ts + en.ts: nowa sekcja `privacy` z 6 podsekcjami (admin, formularz, Umami analytics, cookies, prawa, zmiany)
- PrivacyPolicyPage.tsx: strona polityki używająca useT(), sekcje z kotwicami (controller/contact/analytics/cookies/rights/changes)
- App.tsx: prosty routing po window.location.pathname — /privacy → PrivacyPolicyPage
- Footer.tsx: /privacy i /privacy#cookies zamiast martwych href="#"
Co do sekcji Umami: napisana na podstawie kodu (self-hosted stats.gethumanai.pl, cookie-free). Zweryfikuj treść przed publikacją.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fire window.umami?.track('umow-rozmowe') right after the mailer returns
200 (status -> 'success'), so the conversion goal counts only real sends,
not validation errors, network failures, or button clicks. Optional-chain
guard keeps it a no-op on dev/localhost where the tracker isn't loaded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inject the Umami tracking script into index.html via a build-only Vite
plugin (apply: 'build'), so it loads on production but never on the dev
server. Website id comes from PUBLIC_UMAMI_WEBSITE_ID with a fallback to
the gethumanai.pl stats id. The SPA serves PL and EN from the same
index.html, so both language versions get the tag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- zamiana wszystkich adresów e-mail @gethumanai.com -> @gethumanai.pl
(CTASection, Footer, i18n pl/en)
- walidacja po stronie klienta przed wysłaniem (name niepuste,
email zawiera "@", message >= 5 znaków — zgodne z mailer.mjs)
- mapowanie kodów błędów mailera na czytelne komunikaty w i18n (pl/en),
pokazywane zamiast generycznego błędu; honeypot company bez zmian
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Context 'hetzner' → ssh://oskar@135.181.153.108, network npm_default
(NPM runs as 'npm' project on Hetzner, not 'nginxproxymanager' as on piha)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mailer/: standalone Express+nodemailer service on port 3000 with
validation, honeypot (company field), per-IP rate limiting (5/min),
Fastmail SMTP. Returns 400 on bad input, 429 on rate limit, 502 on
SMTP failure, 200 {ok:true} on success or honeypot hit.
- Dockerfile: multi-stage nginx build for humanai-landing container.
- mailer/Dockerfile: node:22-alpine, no exposed host ports.
- deploy.sh: idempotent deploy of both containers to piha.local via
Docker context 'piha', with smoke tests (400 on empty body, 200 on
honeypot fill).
- src/i18n/: minimal pl/en translation files; useT() hook picks
language from navigator.language.
- CTASection: replaced dead onSubmit with fetch POST /api/contact,
added hidden honeypot field, success/error states using i18n strings.
- README: Mailer section with two manual steps (credentials + NPM proxy).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>