Všechny projekty

Osobní projekt

PlainlySmart

Přehled

PlainlySmart začal jako jednoduchý marketingový web pro custom software business, ale od začátku jsem k němu přistupoval spíš jako k production aplikaci než jen jako k sadě statických stránek. Cílem bylo zachovat frontend kompletně staticky deployovatelný a přitom podporovat localization, theme persistence, interaktivní consultation flow, campaign attribution a bezpečnou komunikaci s externím backendem. Výsledkem je lehký Next.js web, který v runtime nepotřebuje Node.js server, ale v browseru se stále chová jako moderní aplikace.

Moje role

  • Web jsem navrhl a postavil end to end, od vizuální struktury a responsive komponent přes localization, browser-side state a accessibility až po contact workflow. Použil jsem Next.js App Router s TypeScriptem a Tailwind CSS 4 a následně přidal Motion na vybraná místa tak, aby rozhraní působilo živěji, ale animation nebyla podmínkou použitelnosti.
  • Jak se projekt vyvíjel, posunul jsem contact flow od jednoduchých email linků k reusable consultation dialogu. React Hook Form nyní spravuje form state, Zod validation a normalization a next-intl poskytuje localized validation messages. Každá část tak má jasnou odpovědnost místo toho, aby se veškerá form logic postupně hromadila v jedné komponentě.
  • Navrhl jsem také integrační hranici pro samostatné contact API. Statický frontend sesbírá lead, doplní locale a campaign attribution, získá Cloudflare Turnstile token a odešle request na externí endpoint. Frontend a backend infrastructure se díky tomu mohou vyvíjet nezávisle na sobě.

Architektura a technologie

  • Aplikace se exportuje jako statické HTML, CSS a JavaScript, takže může být hostovaná bez Next.js serveru. Dynamic operations jako contact submission jsou řešené přes externí API místo Next.js server routes.
  • Localization je postavená na next-intl se symetrickými routes /en a /cs. Explicitní URL mají přednost před browser detection, language preference se ukládá client-side a theme state zůstává zachovaný při přepínání locale bez vzniku hydration mismatch.
  • Consultation experience je sdílený napříč stránkou přes React Context. Jednotlivé CTA mohou otevřít stejný native dialog bez prop drilling, zatímco dialog lifecycle zůstává oddělený od samotné implementace formuláře.
  • Contact form používá React Hook Form společně se Zod schema. Zod trimuje a validuje odesílané hodnoty, React Hook Form řeší field registration, validation errors a submission state a accessibility propojení chyb s inputy je řešené přes aria-invalid a aria-describedby.
  • Cloudflare Turnstile je integrovaný přes jeho explicit JavaScript API. Malý React wrapper načte externí script, vyrenderuje widget pomocí window.turnstile.render, předá verification token parent formuláři a při neúspěšném submission vytvoří nový widget s čerstvým tokenem.
  • Campaign attribution se načítá ze standardních UTM parametrů a ukládá do sessionStorage s in-memory fallbackem. Hodnoty tak následují uživatele během navigation a při odeslání contact requestu se připojí společně s aktuálním locale.
  • Playwright browser tests testují skutečný static export, ne pouze development server. Pokrývají localization, theme persistence, scénáře s blocked storage, navigation behaviour a browser-level JavaScript errors.

Technické výzvy

  • Jedním z hlavních architectural decisions bylo zachovat web skutečně statický bez ztráty application-like behaviour. Místo přidání Next.js runtime pouze kvůli jednomu formuláři jsem oddělil frontend od budoucího contact backendu a navrhl komunikaci browseru s dedikovaným API endpointem.
  • Localization přinesla víc problémů než jen překlad textů. Web musí při první návštěvě zvolit správný jazyk, respektovat explicitní locale URL, pamatovat si user preference, fungovat i při nedostupném browser storage a udržet dark mode při plné locale navigation bez hydration flash.
  • Konzultační formulář postupně nabral validace, ochranu před duplicitním odesláním, status handling a data extrační logiku. Refactor na React Hook Form a Zod komponentu zjednodušil tím, že form state a validation mají jasně definované vlastníky, zatímco Turnstile, tracking a odesílání přes API zůstaly oddělené jako samostatné části aplikační logiky.
  • Integrace Turnstile vyžadovala propojit deklarativní React lifecycle s imperativním third-party API. Komponenta musí počkat na načtení externího scriptu, vyrenderovat widget do konkrétního DOM elementu, při cleanup odstranit předchozí instanci widgetu a po zneplatnění verification tokenu vygenerovat nový.
  • UTM tracking bylo potřeba navrhnout defensivně. Browser storage nemusí být dostupný, uloženému JSONu nelze důvěřovat a campaign values musí přežít client-side navigation. Implementace proto validuje obnovená data a používá in-memory fallback místo toho, aby tracking code mohl ovlivnit samotný contact flow.

Výsledky a přínos

  • Projekt se vyvinul ze statické firemní landing page na produkčně orientovanou marketingovou aplikaci a přitom si zachoval plně statický model nasazení.
  • Návštěvníci mohou přepínat mezi anglickou a českou verzí, zachovat si preferenci motivu a otevřít stejný zážitek z konzultace z více částí webu bez duplikace logiky dialogu.
  • Kontaktní workflow nyní používá typovaný stav formuláře, validaci řízenou schématem, lokalizované chyby, ověření Turnstile, UTM atribuci a čistou integrační hranici pro externí API místo původního odesílání přes mailto.
  • Architektura umožňuje přidat backend nezávisle, například pomocí AWS API Gateway a Lambda, bez změny modelu statického hostingu frontendu.
  • Projekt mi zároveň poskytl praktické prostředí pro řešení problémů, které u jednodušších marketingových webů často nejsou vidět: chování hydratace, přístupnost, selhání úložiště prohlížeče, životní cyklus skriptů třetích stran, testování statického exportu a hranice důvěry mezi klientem a serverem.