PageSourceSearch

https://onocon.netlify.app/js/emails.js

js onocon.netlify.app collected 2026-10-03 11:13:45 UTC 3,284 bytes, 93 lines download raw bytes

1/* ─── Email Notifications via Netlify Function + Resend ─────────
2   Frontend never touches the API key — calls /.netlify/functions/send-email
3──────────────────────────────────────────────────────────────── */
4
5const Emails = {
6  _timer: null,
7
8  _cancel() {
9    if (this._timer) { clearTimeout(this._timer); this._timer = null; }
10  },
11
12  _send(type, data) {
13    fetch("/.netlify/functions/send-email", {
14      method:  "POST",
15      headers: { "Content-Type": "application/json" },
16      body:    JSON.stringify({ type, data })
17    }).catch(() => {});  // fail silently — never block UX
18  },
19
20  _payload(state) {
21    return {
22      client_name:      state.contact.firstName + " " + state.contact.lastName,
23      client_email:     state.contact.email,
24      client_phone:     state.contact.phone,
25      referral:         state.contact.referral,
26      property_address: state.address.display.split(",").slice(0, 3).join(",")
27    };
28  },
29
30  // Called when contact submitted — waits 5 min before sending
31  newLead(state) {
32    this._cancel();
33    const data = this._payload(state);
34    this._timer = setTimeout(() => {
35      this._send("lead", data);
36    }, 300000); // 5 minutes
37  },
38
39  // Called when quote is shown — cancels lead timer, waits 2 min
40  quoteViewed(state, quote) {
41    this._cancel();
42    const freqLabel = { weekly: "Weekly", biweekly: "Bi-Weekly", monthly: "Monthly" }[state.frequency];
43    const terrainLabel = {
44      flat: "Flat", slightly_inclined: "Slightly Inclined",
45      moderately_inclined: "Moderately Hilly", steep: "Steep / Hilly"
46    }[state.terrain];
47
48    const data = {
49      ...this._payload(state),
50      lawn_sqft:         state.sqFt + " sq ft",
51      frequency:         freqLabel,
52      terrain:           terrainLabel,
53      restricted_access: state.hasRestrictedAccess ? "Yes" : "No",
54      price_per_visit:   "$" + quote.perVisit.toFixed(2),
55      price_per_month:   "$" + quote.perMonth.toFixed(2) + " / month (" + quote.visitsPerMonth + " visits)"
56    };
57
58    this._timer = setTimeout(() => {
59      this._send("viewed", data);
60    }, 120000); // 2 minutes
61  },
62
63  // Called when client confirms — cancels any timer, sends immediately
64  quoteSecured(state, quote) {
65    this._cancel();
66    const freqLabel = { weekly: "Weekly", biweekly: "Bi-Weekly", monthly: "Monthly" }[state.frequency];
67
68    this._send("secured", {
69      ...this._payload(state),
70      lawn_sqft:       state.sqFt + " sq ft",
71      frequency:       freqLabel,
72      price_per_visit: "$" + quote.perVisit.toFixed(2),
73      price_per_month: "$" + quote.perMonth.toFixed(2) + " / month (" + quote.visitsPerMonth + " visits)"
74    });
75  },
76
77  test() {
78    return fetch("/.netlify/functions/send-email", {
79      method:  "POST",
80      headers: { "Content-Type": "application/json" },
81      body: JSON.stringify({
82        type: "lead",
83        data: {
84          client_name:      "Test Client",
85          client_email:     "[email protected]",
86          client_phone:     "(416) 555-0100",
87          referral:         "Test",
88          property_address: "123 Maple Ave, Mississauga, ON"
89        }
90      })
91    });
92  }
93};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.