From 09b9bf90bd86d3ee4195d95f36a3d522fd66a725 Mon Sep 17 00:00:00 2001 From: Oskar Kapala Date: Tue, 23 Jun 2026 16:26:22 +0200 Subject: [PATCH] front: poprawny adres kontaktowy + walidacja formularza MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/components/CTASection.tsx | 69 +++++++++++++++++++++++++++++++++-- src/components/Footer.tsx | 4 +- src/i18n/en.ts | 10 ++++- src/i18n/pl.ts | 10 ++++- 4 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/components/CTASection.tsx b/src/components/CTASection.tsx index 500faf4..0221b7d 100644 --- a/src/components/CTASection.tsx +++ b/src/components/CTASection.tsx @@ -12,9 +12,46 @@ const CTASection = () => { const [message, setMessage] = useState(''); const [company, setCompany] = useState(''); // honeypot const [status, setStatus] = useState('idle'); + const [fieldErrors, setFieldErrors] = useState<{ + name?: string; + email?: string; + message?: string; + }>({}); + const [errorMsg, setErrorMsg] = useState(''); + + // Map mailer error codes (mailer.mjs) to human-readable, localized text. + const mapBackendError = (code: string) => { + switch (code) { + case 'name required': + return t.contact.errors.nameRequired; + case 'valid email required': + return t.contact.errors.emailInvalid; + case 'message too short': + return t.contact.errors.messageTooShort; + case 'Too many requests': + return t.contact.errors.rateLimited; + case 'mail delivery failed': + return t.contact.errors.mailFailed; + default: + return t.contact.errors.generic; + } + }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + + // Client-side validation — kept in sync with mailer.mjs thresholds. + const errs: { name?: string; email?: string; message?: string } = {}; + if (!name.trim()) errs.name = t.contact.errors.nameRequired; + if (!email.includes('@')) errs.email = t.contact.errors.emailInvalid; + if (message.trim().length < 5) errs.message = t.contact.errors.messageTooShort; + setFieldErrors(errs); + if (Object.keys(errs).length > 0) { + if (status !== 'idle') setStatus('idle'); + return; + } + + setErrorMsg(''); setStatus('sending'); try { const res = await fetch('/api/contact', { @@ -22,9 +59,21 @@ const CTASection = () => { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, message, company }), }); - if (!res.ok) throw new Error(`${res.status}`); + if (!res.ok) { + let code = ''; + try { + const data = await res.json(); + code = typeof data?.error === 'string' ? data.error : ''; + } catch { + /* response had no JSON body */ + } + setErrorMsg(mapBackendError(code)); + setStatus('error'); + return; + } setStatus('success'); } catch { + setErrorMsg(t.contact.errors.generic); setStatus('error'); } }; @@ -50,8 +99,8 @@ const CTASection = () => {
- - hello@gethumanai.com + + hello@gethumanai.pl

@@ -97,8 +146,12 @@ const CTASection = () => { value={name} onChange={(e) => setName(e.target.value)} required + aria-invalid={!!fieldErrors.name} className="w-full bg-background border border-white/10 rounded-lg px-4 py-3 focus:outline-none focus:border-primary/50 transition-colors" /> + {fieldErrors.name && ( +

{fieldErrors.name}

+ )}
@@ -108,8 +161,12 @@ const CTASection = () => { value={email} onChange={(e) => setEmail(e.target.value)} required + aria-invalid={!!fieldErrors.email} className="w-full bg-background border border-white/10 rounded-lg px-4 py-3 focus:outline-none focus:border-primary/50 transition-colors" /> + {fieldErrors.email && ( +

{fieldErrors.email}

+ )}
@@ -120,14 +177,18 @@ const CTASection = () => { value={message} onChange={(e) => setMessage(e.target.value)} required + aria-invalid={!!fieldErrors.message} className="w-full bg-background border border-white/10 rounded-lg px-4 py-3 focus:outline-none focus:border-primary/50 transition-colors resize-none" /> + {fieldErrors.message && ( +

{fieldErrors.message}

+ )}
{status === 'error' && (

{t.contact.errorTitle}{' '} - {t.contact.errorBody} + {errorMsg || t.contact.errorBody}

)} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 0fc3b52..7d5d60b 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -14,8 +14,8 @@ const Footer = () => {

ludzka strona AI — budujemy praktyczne systemy AI dla ludzi i organizacji.

- - hello@gethumanai.com + + hello@gethumanai.pl diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 1129073..873a55b 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -11,6 +11,14 @@ export const en = { successTitle: 'Message sent!', successBody: "We'll get back to you shortly.", errorTitle: 'Something went wrong.', - errorBody: 'Please try again or email us directly at hello@gethumanai.com.', + errorBody: 'Please try again or email us directly at hello@gethumanai.pl.', + errors: { + nameRequired: 'Please enter your name.', + emailInvalid: 'Please enter a valid email address.', + messageTooShort: 'Your message must be at least 5 characters long.', + rateLimited: 'Too many attempts. Please wait a moment and try again.', + mailFailed: 'We could not send your message. Please try again or email us directly at hello@gethumanai.pl.', + generic: 'Please try again or email us directly at hello@gethumanai.pl.', + }, }, }; diff --git a/src/i18n/pl.ts b/src/i18n/pl.ts index 51ead77..b6d5aff 100644 --- a/src/i18n/pl.ts +++ b/src/i18n/pl.ts @@ -11,6 +11,14 @@ export const pl = { successTitle: 'Wiadomość wysłana!', successBody: 'Odezwiemy się wkrótce.', errorTitle: 'Coś poszło nie tak.', - errorBody: 'Spróbuj ponownie lub napisz bezpośrednio na hello@gethumanai.com.', + errorBody: 'Spróbuj ponownie lub napisz bezpośrednio na hello@gethumanai.pl.', + errors: { + nameRequired: 'Podaj imię.', + emailInvalid: 'Podaj poprawny adres e-mail.', + messageTooShort: 'Wiadomość musi mieć co najmniej 5 znaków.', + rateLimited: 'Zbyt wiele prób. Odczekaj chwilę i spróbuj ponownie.', + mailFailed: 'Nie udało się wysłać wiadomości. Spróbuj ponownie lub napisz bezpośrednio na hello@gethumanai.pl.', + generic: 'Spróbuj ponownie lub napisz bezpośrednio na hello@gethumanai.pl.', + }, }, };