const { useState, useEffect, useRef } = React;
function IntakeModal({ onClose }) {
const [step, setStep] = useState(0); // 0 property, 1 owner, 2 review, 3 success
const tier = 'Frontline';
const [form, setForm] = useState({
cad: '',
address: '',
city: 'Galveston',
zip: '',
name: '',
email: '',
phone: '',
});
const [errors, setErrors] = useState({});
const [submitting, setSubmitting] = useState(false);
const firstField = useRef(null);
useEffect(() => {
if (firstField.current) firstField.current.focus();
function key(e) { if (e.key === 'Escape') onClose(); }
window.addEventListener('keydown', key);
document.body.style.overflow = 'hidden';
return () => {
window.removeEventListener('keydown', key);
document.body.style.overflow = '';
};
}, []);
function update(field, value) {
setForm(f => ({ ...f, [field]: value }));
setErrors(e => ({ ...e, [field]: undefined }));
}
function validateStep(s) {
const errs = {};
if (s === 0) {
if (!/^R-?\d{6,8}$/i.test(form.cad.trim())) errs.cad = 'Use the R-number from your Notice (e.g. R-145203).';
if (form.address.trim().length < 5) errs.address = 'Required.';
if (!/^\d{5}$/.test(form.zip.trim())) errs.zip = 'Five-digit zip.';
}
if (s === 1) {
if (form.name.trim().length < 2) errs.name = 'Required.';
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email.trim())) errs.email = 'Use a real email — that\'s where the packet goes.';
if (form.phone && !/^[\d\s\-().+]{7,}$/.test(form.phone)) errs.phone = 'Optional — but if filled, use a real number.';
}
return errs;
}
function next() {
const errs = validateStep(step);
if (Object.keys(errs).length) { setErrors(errs); return; }
if (step === 2) { submit(); return; }
setStep(s => s + 1);
}
function back() {
setStep(s => Math.max(0, s - 1));
}
function submit() {
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
setStep(3);
}, 1400);
}
const conf = 'LP-' + Math.random().toString(36).slice(2, 7).toUpperCase() + '-' + new Date().getFullYear();
return (
{ if (e.target.classList.contains('modal-backdrop')) onClose(); }}>
Latchpin Tax · Intake
{step === 3 ? 'Packet queued.' : 'Start your protest'}
×
{step < 3 && (
<>
= 0 ? 'on' : ''}>
= 1 ? 'on' : ''}>
= 2 ? 'on' : ''}>
{step === 0 && (
<>
Galveston CAD account number
update('cad', e.target.value)}
placeholder="R-145203"
className={errors.cad ? 'err' : ''}
/>
{errors.cad &&
{errors.cad}
}
Property street address
update('address', e.target.value)}
placeholder="2418 Pelican Loop"
className={errors.address ? 'err' : ''}
/>
{errors.address &&
{errors.address}
}
>
)}
{step === 1 && (
<>
Owner name (as on the Notice)
update('name', e.target.value)}
placeholder="Jordan A. Reyes"
className={errors.name ? 'err' : ''}
/>
{errors.name &&
{errors.name}
}
Email — where the packet goes
update('email', e.target.value)}
placeholder="jordan@example.com"
className={errors.email ? 'err' : ''}
/>
{errors.email &&
{errors.email}
}
Phone (optional)
update('phone', e.target.value)}
placeholder="(409) 555-0182"
className={errors.phone ? 'err' : ''}
/>
{errors.phone &&
{errors.phone}
}
We use this email once — to send your packet. No newsletter. No marketing. No resale. See
privacy policy .
>
)}
{step === 2 && (
<>
Packet Frontline · $149
CAD account {form.cad.toUpperCase()}
Property {form.address}, {form.city} {form.zip}
Owner {form.name}
Delivery email {form.email}
ETA < 5 minutes
By clicking
Pay $149 , you authorize a one-time charge to your card and confirm that you are the property owner of record (or a person authorized by them) and have read the
TDLR disclosure in our footer. Latchpin Tax is not a TDLR-licensed property tax consultant.
>
)}
← Back
{submitting ? 'Building packet…' : step === 2 ? 'Pay $149 →' : 'Continue →'}
>
)}
{step === 3 && (
✓
Packet queued. Check your inbox in 5 minutes.
We're pulling your CAD record and same-street comparables from the Galveston County ArcGIS service right now. You'll get a zip with all five documents at {form.email} .
Confirmation {conf}
Packet Frontline · $149
Account {form.cad.toUpperCase()}
Property {form.address}
ETA < 5 min
Close
)}
);
}
function CTAManager() {
const [open, setOpen] = useState(false);
useEffect(() => {
function handle(e) {
const btn = e.target.closest('[data-cta]');
if (!btn) return;
e.preventDefault();
setOpen(true);
}
document.addEventListener('click', handle);
return () => document.removeEventListener('click', handle);
}, []);
if (!open) return null;
return setOpen(false)} />;
}
ReactDOM.createRoot(document.getElementById('modal-mount')).render( );