Commit graph

15 commits

Author SHA1 Message Date
oskar 3e452191de feat: wybór języka przez URL (/en) — reaktywny hook, przełącznik, hreflang
Język wynikał z navigator.language liczonego przy każdym renderze, więc nie
dało się go wybrać ani zalinkować. Teraz nośnikiem języka jest ścieżka.

- src/router.ts: usePathname() na useSyncExternalStore + navigate(). pushState
  nie emituje zdarzenia, więc bez tego React nie widzi zmiany URL-a.
- i18n: useLang(), localizePath(), stripLangPrefix(), setLang(). useT() czyta
  język ze ścieżki i jest reaktywne na nawigację.
- Detekcja przeglądarki tylko przy pierwszym wejściu na "/" i tylko gdy w
  localStorage nic nie ma; wynik zapisujemy od razu, więc przekierowanie na
  /en zadziała najwyżej raz i nie nadpisze późniejszego wyboru użytkownika.
  Deep linki honorujemy bez ruszania.
- App.tsx: jedna tablica tras dla obu języków (/en/privacy -> '/privacy').
  Ustawia też document.documentElement.lang, bo index.html ma na sztywno "pl".
- Header: przełącznik PL/EN (desktop + mobile), <button>, nie <a> — link
  przeładowałby dokument.
- Hrefy w Header/Footer/PrivacyPolicyPage/PlatformPage przez localizePath,
  inaczej /en jest pułapką jednokierunkową: każdy klik wraca na PL.
- index.html: hreflang pl/en/x-default; og:url z gethumanai.com na .pl
  (wskazywał na cudzą domenę).

Bez nowych tłumaczeń treści — to osobny krok.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:54:49 +02:00
oskar f062e23dba fix: logo w stopce nawiguje na stronę główną z podstron
Logo miało href="#", więc na /privacy i /platforma klikało w pustkę — ta sama
usterka co wcześniej w linkach "Oferta"/"Kontakt" w tym samym pliku. Cel to
teraz "/", a onClick przechwytuje klik na samej stronie głównej i przewija
płynnie do góry, zamiast przeładowywać stronę.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:03:37 +02:00
oskar f3fb0a244d fix: linki nawigacyjne działają z podstron /privacy i /platforma
Kotwice #offerings/#process/#contact istnieją tylko na stronie głównej, więc
z podstron prowadziły w pustkę. Zmienione na ścieżki bezwzględne (/#offerings,
/#process, /#contact) w Header.tsx i Footer.tsx.

Sama zmiana href nie wystarczyła: sekcje renderuje React, więc przy nawigacji
z podstrony przeglądarka rozwiązuje kotwicę zanim element zaistnieje i zostaje
na górze strony (zweryfikowane w Chrome — scrollY=0 przy sekcji na 1453px).
App.tsx ponawia więc przewinięcie po zamontowaniu sekcji. Kliknięcia w obrębie
strony głównej tego nie dotyczą — to nawigacja w tym samym dokumencie, którą
przeglądarka obsługuje natywnie i płynnie, bez przeładowania.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:56:58 +02:00
oskar 9bb8795f73 chore: ignoruj roboczy CONTENT_EXPORT.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:49:06 +02:00
oskar 8c54b6004c feat: dodaj podstronę /platforma (nadzór nad systemami agentowymi)
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>
2026-07-31 14:49:06 +02:00
oskar e0557a7d76 feat: dodaj stronę polityki prywatności (PL+EN) z routingiem
- 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>
2026-06-25 12:53:49 +02:00
oskar 144dff7f85 Track Umami goal 'umow-rozmowe' on successful contact submit
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>
2026-06-24 17:58:24 +02:00
oskar b519b49e0d Add Umami analytics tag to <head> on production builds
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>
2026-06-24 17:00:42 +02:00
Oskar Kapala efc6ecf3c8 front: dodaj support@gethumanai.pl w sekcji kontakt (CTASection)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:50:15 +02:00
Oskar Kapala 7a6e399379 front: dodaj support@gethumanai.pl obok hello@gethumanai.pl w stopce kontaktu
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:46:45 +02:00
Oskar Kapala 09b9bf90bd front: poprawny adres kontaktowy + walidacja formularza
- 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>
2026-06-23 16:26:22 +02:00
oskar 2067c79c67 fix deploy.sh: target Hetzner VPS instead of piha.local homelab
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>
2026-06-18 15:22:04 +02:00
oskar 39d0662197 Add contact form mailer microservice and i18n support
- 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>
2026-06-18 15:12:05 +02:00
Oskar Kapala a8bb798d03 Complete landing page implementation: all sections, responsive design, SEO
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-12 19:10:59 +02:00
Oskar Kapala 737797f536 Initial commit: project structure and header component
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-12 19:09:50 +02:00