const { useState, useEffect } = React;
// SVG flag components — true-to-spec, used in Direct Contact and footer
const FlagUS = ({ size = 28 }) => (
);
const FlagIN = ({ size = 28 }) => (
);
const CTAFooter = () => {
return (
<>
{/* Big CTA */}
{/* Footer */}
>
);
};
const ContactForm = () => {
const [form, setForm] = useState({
name: '', email: '', company: '', role: '', message: '',
captcha_answer: '', hp: '',
});
const [captcha, setCaptcha] = useState({ id: '', question: 'Loading…', loading: true, error: false });
const [status, setStatus] = useState({ state: 'idle', msg: '' }); // idle | sending | success | error
const isFile = typeof window !== 'undefined' && window.location && window.location.protocol === 'file:';
const loadCaptcha = async () => {
setCaptcha(c => ({ ...c, loading: true, error: false }));
if (isFile) {
// Demo mode — generate a captcha client-side (server PHP can't run on file://)
const a = 2 + Math.floor(Math.random() * 8);
const b = 2 + Math.floor(Math.random() * 8);
setCaptcha({ id: `demo:${a + b}`, question: `What is ${a} + ${b}?`, loading: false, error: false });
return;
}
try {
const r = await fetch('api/captcha.php', { cache: 'no-store', credentials: 'same-origin' });
if (!r.ok) throw new Error('captcha_fetch');
const j = await r.json();
if (!j.id) throw new Error('captcha_payload');
setCaptcha({ id: j.id, question: j.question, loading: false, error: false });
} catch (e) {
setCaptcha({ id: '', question: 'Could not load captcha — refresh', loading: false, error: true });
}
};
useEffect(() => { loadCaptcha(); }, []);
const update = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value }));
const submit = async (e) => {
e.preventDefault();
if (status.state === 'sending') return;
// Client-side validation mirroring the server
if (!form.name.trim()) return setStatus({ state: 'error', msg: 'Please enter your name.' });
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) return setStatus({ state: 'error', msg: 'Please enter a valid work email.' });
if (!form.message.trim()) return setStatus({ state: 'error', msg: 'Tell us briefly what you want to automate.' });
if (!form.captcha_answer.trim()) return setStatus({ state: 'error', msg: 'Please solve the captcha.' });
setStatus({ state: 'sending', msg: '' });
if (isFile) {
// Demo mode — verify captcha locally, then pretend success
const expected = captcha.id.replace(/^demo:/, '');
await new Promise(r => setTimeout(r, 600));
if (parseInt(form.captcha_answer, 10) !== parseInt(expected, 10)) {
setStatus({ state: 'error', msg: 'Captcha answer is incorrect.' });
loadCaptcha();
setForm(f => ({ ...f, captcha_answer: '' }));
return;
}
setStatus({ state: 'success', msg: 'Demo only — running off file://. On the deployed PHP host this would email connect@rocketberry.co.' });
return;
}
try {
const r = await fetch('api/mail.php', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: form.name, email: form.email, company: form.company,
role: form.role, message: form.message,
captcha_id: captcha.id, captcha_answer: form.captcha_answer,
hp: form.hp,
}),
});
const j = await r.json().catch(() => ({}));
if (r.ok && j.ok) {
setStatus({ state: 'success', msg: 'Thanks — a Rocketberry AI engineer will reach out within one business day.' });
setForm({ name: '', email: '', company: '', role: '', message: '', captcha_answer: '', hp: '' });
loadCaptcha();
return;
}
const map = {
invalid_name: 'Please enter a valid name.',
invalid_email: 'Please enter a valid email address.',
invalid_message: 'Please describe what you want to automate.',
captcha_required:'Please solve the captcha.',
captcha_invalid: 'Captcha is invalid — refreshing…',
captcha_expired: 'Captcha expired — refreshing…',
captcha_wrong: 'Captcha answer is incorrect.',
too_many_links: 'Please remove most links from your message.',
rate_limited: 'Too many submissions from this network — please try again later.',
origin_not_allowed: 'This origin is not authorised to submit.',
send_failed: 'Email server temporarily unavailable — please try again or call us directly.',
};
setStatus({ state: 'error', msg: map[j.error] || 'Something went wrong — please try again.' });
if (['captcha_invalid', 'captcha_expired', 'captcha_wrong'].includes(j.error)) {
loadCaptcha();
setForm(f => ({ ...f, captcha_answer: '' }));
}
} catch (err) {
setStatus({ state: 'error', msg: 'Network error — please try again.' });
}
};
if (status.state === 'success') {
return (
Message sent
We've got your brief.
{status.msg}
);
}
return (
);
};
const ContactOption = ({icon, title, desc, cta}) => {
const IconCmp = I[icon];
return (
);
};
const FooterCol = ({title, links}) => (
{title}
{links.map(l => (
- {l}
))}
);
const OfficeCard = ({country, flag, color, city, lines}) => (
{city}
{lines.map((l, i) =>
{l}
)}
);
// ── Form field primitive (used by ContactForm) ──
const Field = ({label, placeholder, type='text', value, onChange, required, maxLength}) => (
);
window.CTAFooter = CTAFooter;