/* IJWestern — the site's two forms (client, 3 Nov 2026).
   Globals: VisitForm, ContactForm, visitGrounds

   The two forms that came before these (step 5's visit form and step
   9's contact form) are gone. These replace them:

   - `VisitForm` is "Schedule a visit". It opens in the shared modal from
     every Schedule-a-visit button and from the project hero's Buy now,
     and sits inline in the home page's "One form is all it takes"
     section. Two choices: an in-person site visit (project, date and
     time, all required) or a call back.
   - `ContactForm` is the general form, on the Contact page only.

   Both share the name, phone and email fields, the confirmation panel
   and one submit hook, `ijwSubmit`, which is the single place the
   database goes in.

   Loaded on all nine pages, after shell.jsx and before modal.jsx.
   Hooks are read off `React.` rather than destructured: every page's
   own entry file destructures them at the top level, and a second
   `const { useState }` in a shared file would collide with it. */

/* ── Submission ─────────────────────────────────────────────────────
   Sends the form to the site's own server (worker/index.js), which
   checks it and saves it to the database the team dashboard reads.
   Resolves when it is saved; rejects with a message a visitor can read
   when it is not, and the form shows that message with the office
   number, so nobody leaves thinking they were heard when they were not.
   `kind` is "visit" or "contact". */
const FM_OFFICE = "+91 91485 21696";
async function ijwSubmit(kind, payload) {
  let res;
  try {
    res = await fetch("/api/enquiry", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      // The anonymous browser ID from track.js, only if the visitor tapped OK
      // on the cookie notice. It lets the dashboard show how they found us.
      body: JSON.stringify(Object.assign({}, payload,
        window.ijwTrack && window.ijwTrack.vid() ? { vid: window.ijwTrack.vid() } : {})),
    });
  } catch (e) {
    throw new Error("We couldn't reach our server. Check your connection and try again.");
  }
  let body = {};
  try { body = await res.json(); } catch (e) {}
  if (!res.ok) {
    const err = new Error(body.error || "Something went wrong sending this.");
    err.status = res.status;
    throw err;
  }
  return body;
}

/* The visit hours already taken, as a Set of "YYYY-MM-DD HH:00" (India
   time). One visit per hour across all five projects, so a taken hour is
   taken whichever project is picked. The server checks again on submit;
   this only spares the reader picking an hour that is gone. If the list
   cannot be fetched, every hour stays open and the server decides. */
function useTakenSlots() {
  const [taken, setTaken] = React.useState(() => new Set());
  const load = React.useCallback(() => {
    return fetch("/api/slots", { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : { taken: [] }))
      .then((j) => { const t = new Set(j.taken || []); setTaken(t); return t; })
      .catch(() => taken);
  }, []);
  React.useEffect(() => { load(); }, [load]);
  return [taken, load];
}

/* ── Country codes ──────────────────────────────────────────────────
   ISO code | name | dialling code. India leads, so it is both the
   default and the first thing in the list; the rest are alphabetical.
   The ISO code is the option's value because dialling codes are not
   unique (+1 is the US and Canada, +44 is the UK and Jersey). */
const FM_COUNTRIES = `IN|India|91
AF|Afghanistan|93
AX|Åland Islands|358
AL|Albania|355
DZ|Algeria|213
AS|American Samoa|1 684
AD|Andorra|376
AO|Angola|244
AI|Anguilla|1 264
AG|Antigua and Barbuda|1 268
AR|Argentina|54
AM|Armenia|374
AW|Aruba|297
AU|Australia|61
AT|Austria|43
AZ|Azerbaijan|994
BS|Bahamas|1 242
BH|Bahrain|973
BD|Bangladesh|880
BB|Barbados|1 246
BY|Belarus|375
BE|Belgium|32
BZ|Belize|501
BJ|Benin|229
BM|Bermuda|1 441
BT|Bhutan|975
BO|Bolivia|591
BA|Bosnia and Herzegovina|387
BW|Botswana|267
BR|Brazil|55
IO|British Indian Ocean Territory|246
VG|British Virgin Islands|1 284
BN|Brunei|673
BG|Bulgaria|359
BF|Burkina Faso|226
BI|Burundi|257
KH|Cambodia|855
CM|Cameroon|237
CA|Canada|1
CV|Cape Verde|238
BQ|Caribbean Netherlands|599
KY|Cayman Islands|1 345
CF|Central African Republic|236
TD|Chad|235
CL|Chile|56
CN|China|86
CX|Christmas Island|61
CC|Cocos (Keeling) Islands|61
CO|Colombia|57
KM|Comoros|269
CG|Congo|242
CD|Congo (DRC)|243
CK|Cook Islands|682
CR|Costa Rica|506
CI|Côte d'Ivoire|225
HR|Croatia|385
CU|Cuba|53
CW|Curaçao|599
CY|Cyprus|357
CZ|Czechia|420
DK|Denmark|45
DJ|Djibouti|253
DM|Dominica|1 767
DO|Dominican Republic|1 809
EC|Ecuador|593
EG|Egypt|20
SV|El Salvador|503
GQ|Equatorial Guinea|240
ER|Eritrea|291
EE|Estonia|372
SZ|Eswatini|268
ET|Ethiopia|251
FK|Falkland Islands|500
FO|Faroe Islands|298
FJ|Fiji|679
FI|Finland|358
FR|France|33
GF|French Guiana|594
PF|French Polynesia|689
GA|Gabon|241
GM|Gambia|220
GE|Georgia|995
DE|Germany|49
GH|Ghana|233
GI|Gibraltar|350
GR|Greece|30
GL|Greenland|299
GD|Grenada|1 473
GP|Guadeloupe|590
GU|Guam|1 671
GT|Guatemala|502
GG|Guernsey|44
GN|Guinea|224
GW|Guinea-Bissau|245
GY|Guyana|592
HT|Haiti|509
HN|Honduras|504
HK|Hong Kong|852
HU|Hungary|36
IS|Iceland|354
ID|Indonesia|62
IR|Iran|98
IQ|Iraq|964
IE|Ireland|353
IM|Isle of Man|44
IL|Israel|972
IT|Italy|39
JM|Jamaica|1 876
JP|Japan|81
JE|Jersey|44
JO|Jordan|962
KZ|Kazakhstan|7
KE|Kenya|254
KI|Kiribati|686
XK|Kosovo|383
KW|Kuwait|965
KG|Kyrgyzstan|996
LA|Laos|856
LV|Latvia|371
LB|Lebanon|961
LS|Lesotho|266
LR|Liberia|231
LY|Libya|218
LI|Liechtenstein|423
LT|Lithuania|370
LU|Luxembourg|352
MO|Macao|853
MG|Madagascar|261
MW|Malawi|265
MY|Malaysia|60
MV|Maldives|960
ML|Mali|223
MT|Malta|356
MH|Marshall Islands|692
MQ|Martinique|596
MR|Mauritania|222
MU|Mauritius|230
YT|Mayotte|262
MX|Mexico|52
FM|Micronesia|691
MD|Moldova|373
MC|Monaco|377
MN|Mongolia|976
ME|Montenegro|382
MS|Montserrat|1 664
MA|Morocco|212
MZ|Mozambique|258
MM|Myanmar|95
NA|Namibia|264
NR|Nauru|674
NP|Nepal|977
NL|Netherlands|31
NC|New Caledonia|687
NZ|New Zealand|64
NI|Nicaragua|505
NE|Niger|227
NG|Nigeria|234
NU|Niue|683
NF|Norfolk Island|672
KP|North Korea|850
MK|North Macedonia|389
MP|Northern Mariana Islands|1 670
NO|Norway|47
OM|Oman|968
PK|Pakistan|92
PW|Palau|680
PS|Palestine|970
PA|Panama|507
PG|Papua New Guinea|675
PY|Paraguay|595
PE|Peru|51
PH|Philippines|63
PL|Poland|48
PT|Portugal|351
PR|Puerto Rico|1 787
QA|Qatar|974
RE|Réunion|262
RO|Romania|40
RU|Russia|7
RW|Rwanda|250
BL|Saint Barthélemy|590
SH|Saint Helena|290
KN|Saint Kitts and Nevis|1 869
LC|Saint Lucia|1 758
MF|Saint Martin|590
PM|Saint Pierre and Miquelon|508
VC|Saint Vincent and the Grenadines|1 784
WS|Samoa|685
SM|San Marino|378
ST|São Tomé and Príncipe|239
SA|Saudi Arabia|966
SN|Senegal|221
RS|Serbia|381
SC|Seychelles|248
SL|Sierra Leone|232
SG|Singapore|65
SX|Sint Maarten|1 721
SK|Slovakia|421
SI|Slovenia|386
SB|Solomon Islands|677
SO|Somalia|252
ZA|South Africa|27
KR|South Korea|82
SS|South Sudan|211
ES|Spain|34
LK|Sri Lanka|94
SD|Sudan|249
SR|Suriname|597
SJ|Svalbard and Jan Mayen|47
SE|Sweden|46
CH|Switzerland|41
SY|Syria|963
TW|Taiwan|886
TJ|Tajikistan|992
TZ|Tanzania|255
TH|Thailand|66
TL|Timor-Leste|670
TG|Togo|228
TK|Tokelau|690
TO|Tonga|676
TT|Trinidad and Tobago|1 868
TN|Tunisia|216
TR|Türkiye|90
TM|Turkmenistan|993
TC|Turks and Caicos Islands|1 649
TV|Tuvalu|688
VI|U.S. Virgin Islands|1 340
UG|Uganda|256
UA|Ukraine|380
AE|United Arab Emirates|971
GB|United Kingdom|44
US|United States|1
UY|Uruguay|598
UZ|Uzbekistan|998
VU|Vanuatu|678
VA|Vatican City|39
VE|Venezuela|58
VN|Vietnam|84
WF|Wallis and Futuna|681
EH|Western Sahara|212
YE|Yemen|967
ZM|Zambia|260
ZW|Zimbabwe|263`
  .split("\n")
  .map((row) => {
    const [iso, name, dial] = row.split("|");
    return { iso, name, dial: `+${dial}` };
  });

const FM_COUNTRY = Object.fromEntries(FM_COUNTRIES.map((c) => [c.iso, c]));

/* ── Visit slots ────────────────────────────────────────────────────
   Every day of the week, from tomorrow. Hourly, 10:00 AM to 6:00 PM,
   nothing after 6 (client, 3 Nov). The value is 24-hour so the
   database gets something sortable; the label is what the reader
   sees. */
const FM_SLOTS = [10, 11, 12, 13, 14, 15, 16, 17, 18].map((h) => ({
  value: `${String(h).padStart(2, "0")}:00`,
  label: `${h > 12 ? h - 12 : h}:00 ${h >= 12 ? "PM" : "AM"}`,
}));

/* Plot size bands for the visit form (client, 3 Nov). The value is what
   the database gets; the label is what the reader sees. */
const FM_PLOT_SIZES = [
  { value: "0-5", label: "0–5 cents" },
  { value: "5-10", label: "5–10 cents" },
  { value: "10+", label: "10+ cents" },
];

const FM_DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const FM_MONTHS = ["January", "February", "March", "April", "May", "June", "July",
                   "August", "September", "October", "November", "December"];

/* Tomorrow in Palakkad (IST, UTC+5:30), not on the reader's own clock:
   a visit is a day at the office, and a reader abroad would otherwise be
   offered a date the server turns down. The server checks the same rule. */
function fmTomorrow() {
  return new Date(Date.now() + 19800000 + 86400000).toISOString().slice(0, 10);
}

function fmLongDate(iso) {
  const [y, m, d] = iso.split("-").map(Number);
  const dt = new Date(y, m - 1, d);
  return `${FM_DAYS[dt.getDay()]}, ${d} ${FM_MONTHS[m - 1]} ${y}`;
}

function fmSlotLabel(v) {
  const s = FM_SLOTS.find((x) => x.value === v);
  return s ? s.label : v;
}

/* ── The five projects, with their locations ────────────────────────
   Read from PROJECT_DATA, which every page loads, in portfolio order.
   The value is the project's raw name, so a CTA can pre-select one by
   passing `ground={p.name}`. PROJECTS is a fallback only. */
function visitGrounds() {
  if (typeof PROJECT_DATA !== "undefined") {
    return Object.entries(PROJECT_DATA)
      .map(([key, p]) => ({ key, name: p.name, place: p.locality, n: p.number }))
      .sort((a, b) => a.n.localeCompare(b.n));
  }
  if (typeof PROJECTS !== "undefined" && Array.isArray(PROJECTS)) {
    return PROJECTS.map((p) => ({ key: p.id, name: p.name, place: p.loc }));
  }
  return [];
}

/* ── Shared fields ───────────────────────────────────────────────── */
function fmPhoneText(code, number) {
  const n = code === "IN" && number.length === 10 ? `${number.slice(0, 5)} ${number.slice(5)}` : number;
  return `${FM_COUNTRY[code].dial} ${n}`;
}

/* The country select is a real <select> laid invisibly over a chip
   that shows only the dialling code. A native select shows its whole
   option text when closed, and "United Kingdom (+44)" does not fit the
   chip; this way the open list carries the names and the closed one
   shows "+44". */
function FmPhone({ id, code, number, onCode, onNumber }) {
  const india = code === "IN";
  return (
    <div className="field">
      <label className="field-k" htmlFor={id}>Mobile number</label>
      <div className="fm-phone">
        <span className="fm-cc">
          <span className="fm-cc-v" aria-hidden="true">{FM_COUNTRY[code].dial}</span>
          <svg className="fm-cc-chev" width="10" height="10" viewBox="0 0 12 12" fill="none"
               stroke="currentColor" strokeWidth="1.4" aria-hidden="true">
            <path d="M2 4.5 L6 8.5 L10 4.5" />
          </svg>
          <select value={code} onChange={(e) => onCode(e.target.value)}
                  aria-label="Country code" autoComplete="tel-country-code">
            {FM_COUNTRIES.map((c) => (
              <option key={c.iso} value={c.iso}>{c.name} ({c.dial})</option>
            ))}
          </select>
        </span>
        <input id={id} type="tel" inputMode="numeric" autoComplete="tel-national" required
               value={number}
               onChange={(e) => onNumber(e.target.value.replace(/\D/g, "").slice(0, 14))}
               pattern={india ? "[6-9][0-9]{9}" : "[0-9]{4,14}"}
               title={india ? "A 10-digit mobile number" : "Your mobile number, digits only"}
               placeholder={india ? "98765 43210" : "Mobile number"} />
      </div>
    </div>
  );
}

function FmNames({ data, set }) {
  return (
    <div className="form-row two">
      <label className="field">
        <span className="field-k">First name</span>
        <input type="text" required autoComplete="given-name" value={data.first}
               onChange={(e) => set("first", e.target.value)} placeholder="First name" />
      </label>
      <label className="field">
        <span className="field-k">Second name</span>
        <input type="text" required autoComplete="family-name" value={data.second}
               onChange={(e) => set("second", e.target.value)} placeholder="Second name" />
      </label>
    </div>
  );
}

function FmEmail({ data, set }) {
  return (
    <label className="field">
      <span className="field-k">Email</span>
      <input type="email" required autoComplete="email" value={data.email}
             onChange={(e) => set("email", e.target.value)} placeholder="you@example.com" />
    </label>
  );
}

function FmLocation({ data, set, required }) {
  return (
    <label className="field">
      <span className="field-k">
        Your location{!required && <> <span className="opt">(optional)</span></>}
      </span>
      <input type="text" required={required} autoComplete="address-level2" value={data.location}
             onChange={(e) => set("location", e.target.value)} placeholder="Town or city" />
    </label>
  );
}

/* A field people never see. Software that fills in every box fills
   this one too, and the server quietly drops what it sends. */
function FmTrap() {
  return (
    <div className="fm-trap" aria-hidden="true">
      <label>Leave this empty<input type="text" name="website" tabIndex={-1} autoComplete="off" defaultValue="" /></label>
    </div>
  );
}

function FmError({ error }) {
  if (!error) return null;
  return (
    <div className="fm-error" role="alert">
      <b>Not sent.</b> {error} Or call us on{" "}
      <a href={"tel:" + FM_OFFICE.replace(/\s/g, "")}>{FM_OFFICE}</a>.
    </div>
  );
}

function FmFoot({ label, status }) {
  return (
    <div className="form-foot">
      <span className="t-mono form-hint">
        By submitting, you share your contact info with the IJW team so we can reach you.
      </span>
      <button type="submit" className="btn btn-primary btn-glass" disabled={status === "sending"}>
        {status === "sending" ? "Sending" : label}
        <span className="arrow"><ArrowIcon /></span>
      </button>
    </div>
  );
}

/* The confirmation that replaces the fields. It takes focus so a screen
   reader announces it, without scrolling: on the home page the form
   gets shorter as this appears and a scroll would jump the page. */
function FmDone({ title, children, again, onAgain }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (ref.current) ref.current.focus({ preventScroll: true });
  }, []);
  return (
    <div className="fm-done" role="status" aria-live="polite">
      <span className="fm-done-tick" aria-hidden="true">
        <svg viewBox="0 0 12 12" width="18" height="18" fill="none" stroke="currentColor"
             strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
          <path d="M2 6.4l2.6 2.6L10 3.2" />
        </svg>
      </span>
      <h3 className="fm-done-title" tabIndex={-1} ref={ref}>{title}</h3>
      <div className="fm-done-text">{children}</div>
      <button type="button" className="fm-done-again" onClick={onAgain}>{again}</button>
    </div>
  );
}

let fmSeq = 0;
function useFmId() {
  const r = React.useRef(null);
  if (r.current === null) r.current = `fm${++fmSeq}`;
  return r.current;
}

/* ── Schedule a visit ───────────────────────────────────────────────
   `ground` is a project's raw name. When a CTA belongs to one project
   (a launch card, the project hero's Buy now, a project page's closing
   CTA) it passes that name and the dropdown starts on it. The header
   and footer pass nothing, and the dropdown opens on "Choose the site
   you want to visit". */
const FM_PICK = "Choose the site you want to visit";

function VisitForm({ className = "register-form", style = null, ground = null }) {
  const grounds = React.useMemo(visitGrounds, []);
  const id = useFmId();
  const minDate = React.useMemo(fmTomorrow, []);
  const blank = () => ({
    first: "", second: "", code: "IN", phone: "", email: "", location: "",
    plot: "",
    mode: "visit",
    project: ground && grounds.some((g) => g.name === ground) ? ground : "",
    date: "", time: "",
  });
  const [data, setData] = React.useState(blank);
  const [status, setStatus] = React.useState("idle");   // idle | sending | sent
  const [sent, setSent] = React.useState(null);
  const [error, setError] = React.useState("");
  const [taken, reloadTaken] = useTakenSlots();
  const set = (k, v) => setData((d) => ({ ...d, [k]: v }));
  const visit = data.mode === "visit";
  const isTaken = (date, time) => !!date && taken.has(date + " " + time);
  /* A new day can make the chosen hour unavailable; drop it if so. */
  const setDate = (v) => setData((d) => ({ ...d, date: v, time: isTaken(v, d.time) ? "" : d.time }));

  const submit = async (e) => {
    e.preventDefault();
    if (status === "sending") return;
    setStatus("sending");
    const payload = {
      form: "schedule-visit",
      firstName: data.first.trim(),
      secondName: data.second.trim(),
      phone: { country: data.code, dial: FM_COUNTRY[data.code].dial, number: data.phone },
      email: data.email.trim(),
      location: data.location.trim(),
      plotSize: data.plot,
      request: visit ? "site-visit" : "call-back",
      ...(visit ? { project: data.project, date: data.date, time: data.time } : {}),
      page: window.location.pathname,
      website: e.target.website ? e.target.website.value : "",
    };
    setError("");
    try {
      await ijwSubmit("visit", payload);
    } catch (err) {
      setError(err.message);
      /* Someone else got the hour first: refresh the list and clear it. */
      if (err.status === 409) { reloadTaken(); set("time", ""); }
      setStatus("idle");
      return;
    }
    setSent({ ...data, first: payload.firstName });
    setStatus("sent");
    if (visit) reloadTaken();
  };

  const again = () => { setData(blank()); setSent(null); setError(""); setStatus("idle"); };

  if (status === "sent" && sent) {
    const phone = fmPhoneText(sent.code, sent.phone);
    return (
      <form className={className} style={style} onSubmit={(e) => e.preventDefault()}>
        {sent.mode === "visit" ? (
          <FmDone title={`Thank you, ${sent.first}.`} again="Make another request" onAgain={again}>
            <p>
              You've asked for an in-person site visit to{" "}
              <strong>{projectNameInline(sent.project)}</strong> on{" "}
              <strong>{fmLongDate(sent.date)}</strong> at{" "}
              <strong>{fmSlotLabel(sent.time)}</strong>.
            </p>
            <p>Our team will contact you on {phone} to confirm.</p>
          </FmDone>
        ) : (
          <FmDone title={`Thank you, ${sent.first}.`} again="Make another request" onAgain={again}>
            <p>
              Our team will call you on <strong>{phone}</strong> within 24 hours.
            </p>
          </FmDone>
        )}
      </form>
    );
  }

  return (
    <form className={className} style={style} onSubmit={submit}>
      <FmNames data={data} set={set} />
      <div className="form-row two">
        <FmPhone id={`${id}-tel`} code={data.code} number={data.phone}
                 onCode={(v) => set("code", v)} onNumber={(v) => set("phone", v)} />
        <FmEmail data={data} set={set} />
      </div>
      {/* Plot size, then location, on one row (client, 3 Nov). */}
      <div className="form-row two">
        <label className="field">
          <span className="field-k">Plot size</span>
          <select required className={data.plot ? "" : "is-empty"} value={data.plot} onChange={(e) => set("plot", e.target.value)}>
            <option value="" disabled>Plot size you're looking for</option>
            {FM_PLOT_SIZES.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
          </select>
        </label>
        <FmLocation data={data} set={set} required />
      </div>

      <fieldset className="fm-choice">
        <legend className="field-k">What would you like</legend>
        <div className="fm-choice-row">
          {[["visit", "Schedule visit"], ["call", "Get a call back"]].map(([v, label]) => (
            <label key={v} className={`fm-radio${data.mode === v ? " is-on" : ""}`}>
              <input type="radio" name={`${id}-mode`} value={v} required
                     checked={data.mode === v} onChange={() => set("mode", v)} />
              <span className="fm-radio-dot" aria-hidden="true" />
              <span>{label}</span>
            </label>
          ))}
        </div>
      </fieldset>

      {/* Mounted either way so it can open and close as a transition.
          `disabled` on the fieldset takes the three fields out of
          validation, out of the tab order and out of the submission
          while the call back is chosen. */}
      <div className={`fm-more${visit ? " is-open" : ""}`} aria-hidden={!visit}>
        <fieldset className="fm-more-in" disabled={!visit}>
          <label className="field">
            <span className="field-k">Project</span>
            <select required className={data.project ? "" : "is-empty"} value={data.project} onChange={(e) => set("project", e.target.value)}>
              <option value="" disabled>{FM_PICK}</option>
              {grounds.map((g) => (
                <option key={g.key} value={g.name}>
                  {projectNameInline(g.name)}{g.place ? ` · ${g.place}` : ""}
                </option>
              ))}
            </select>
          </label>
          <div className="form-row two">
            <label className="field">
              <span className="field-k">Date</span>
              <input type="date" required min={minDate} value={data.date}
                     className={data.date ? "" : "is-empty"}
                     onChange={(e) => setDate(e.target.value)} />
            </label>
            <label className="field">
              <span className="field-k">Time</span>
              <select required className={data.time ? "" : "is-empty"} value={data.time} onChange={(e) => set("time", e.target.value)}>
                <option value="" disabled>Choose a time</option>
                {FM_SLOTS.map((s) => {
                  const gone = isTaken(data.date, s.value);
                  return <option key={s.value} value={s.value} disabled={gone}>{gone ? `${s.label} · Booked` : s.label}</option>;
                })}
              </select>
            </label>
          </div>
        </fieldset>
      </div>

      <FmTrap />
      <FmError error={error} />
      <FmFoot label="Submit" status={status} />
    </form>
  );
}

/* ── General contact ───────────────────────────────────────────────
   The Contact page's form, and nowhere else. Location is the one
   optional field. */
function ContactForm({ className = "register-form", style = null }) {
  const id = useFmId();
  const blank = () => ({
    first: "", second: "", code: "IN", phone: "", email: "", location: "", message: "",
  });
  const [data, setData] = React.useState(blank);
  const [status, setStatus] = React.useState("idle");
  const [sentName, setSentName] = React.useState("");
  const [error, setError] = React.useState("");
  const set = (k, v) => setData((d) => ({ ...d, [k]: v }));

  const submit = async (e) => {
    e.preventDefault();
    if (status === "sending") return;
    setStatus("sending");
    const payload = {
      form: "contact",
      firstName: data.first.trim(),
      secondName: data.second.trim(),
      phone: { country: data.code, dial: FM_COUNTRY[data.code].dial, number: data.phone },
      email: data.email.trim(),
      location: data.location.trim(),
      message: data.message.trim(),
      page: window.location.pathname,
      website: e.target.website ? e.target.website.value : "",
    };
    setError("");
    try {
      await ijwSubmit("contact", payload);
    } catch (err) {
      setError(err.message);
      setStatus("idle");
      return;
    }
    setSentName(payload.firstName);
    setStatus("sent");
  };

  const again = () => { setData(blank()); setError(""); setStatus("idle"); };

  if (status === "sent") {
    return (
      <form className={className} style={style} onSubmit={(e) => e.preventDefault()}>
        <FmDone title={`Thank you, ${sentName}.`} again="Send another message" onAgain={again}>
          <p>We've received your message.</p>
          <p>Our team will be in touch with you shortly.</p>
        </FmDone>
      </form>
    );
  }

  return (
    <form className={className} style={style} onSubmit={submit}>
      <FmNames data={data} set={set} />
      <div className="form-row two">
        <FmPhone id={`${id}-tel`} code={data.code} number={data.phone}
                 onCode={(v) => set("code", v)} onNumber={(v) => set("phone", v)} />
        <FmEmail data={data} set={set} />
      </div>
      <FmLocation data={data} set={set} required={false} />
      <label className="field">
        <span className="field-k">Your message</span>
        <textarea rows="4" required value={data.message}
                  onChange={(e) => set("message", e.target.value)}
                  placeholder="Type your message or question here"></textarea>
      </label>
      <FmTrap />
      <FmError error={error} />
      <FmFoot label="Send message" status={status} />
    </form>
  );
}

Object.assign(window, { VisitForm, ContactForm, visitGrounds, ijwSubmit });
