PageSourceSearch

https://hello-itsme.com/js/contact-demo-inline.js

js hello-itsme.com collected 2026-10-01 15:29:52 UTC 14,159 bytes, 399 lines download raw bytes

1(function (global) {
2  'use strict';
3
4  const BOOKINGS_URL =
5    'https://outlook.office.com/bookwithme/user/[email protected]/meetingtype/nlfstwdnME2vLNYOXr8B3w2?anonymous&ismsaljsauthenabled&ep=mcard';
6
7  const PRIORITY_OPTIONS = [
8    {
9      value:
10        "Want to see how Hello, It's Me® turns short interactive stories into daily social skills adults use at home, work, and in the community?",
11      label:
12        "Want to see how Hello, It's Me® turns short interactive stories into daily social skills adults use at home, work, and in the community?"
13    },
14    {
15      value:
16        'Curious how the Make-A-Friend feature safely matches people and guides first conversations step by step?',
17      label:
18        'Curious how the Make-A-Friend feature safely matches people and guides first conversations step by step?'
19    },
20    {
21      value:
22        "Interested in how your program weaves Hello, It's Me® into care plans, staff workflows, and outcome reporting for adults with IDD?",
23      label:
24        "Interested in how your program weaves Hello, It's Me® into care plans, staff workflows, and outcome reporting for adults with IDD?"
25    }
26  ];
27
28  const SAVE_ERROR_MSG =
29    'Something went wrong. Please try again or email [email protected]';
30
31  let hostEl = null;
32  let rootEl = null;
33  let savedSubmission = null;
34
35  function ensureSupabaseEnv() {
36    return new Promise((resolve) => {
37      if (global.HIM_SUPABASE_CONFIG?.url) {
38        resolve();
39        return;
40      }
41      const wait = setInterval(() => {
42        if (global.HIM_SUPABASE_CONFIG?.url) {
43          clearInterval(wait);
44          resolve();
45        }
46      }, 50);
47      setTimeout(() => {
48        clearInterval(wait);
49        resolve();
50      }, 3000);
51    });
52  }
53
54  function escapeHtml(str) {
55    const div = document.createElement('div');
56    div.textContent = str;
57    return div.innerHTML;
58  }
59
60  function escapeAttr(str) {
61    return escapeHtml(str).replace(/"/g, '"');
62  }
63
64  function buildPriorityCheckboxes() {
65    return PRIORITY_OPTIONS.map(
66      (opt) =>
67        '<label class="contact-demo-check">' +
68        '<input type="checkbox" name="contact_demo_priority" value="' +
69        escapeAttr(opt.value) +
70        '">' +
71        '<span>' + escapeHtml(opt.label) + '</span></label>'
72    ).join('');
73  }
74
75  function buildMarkup() {
76    return (
77      '<div id="contactDemoInline" class="contact-demo-inline" hidden>' +
78      '<div class="contact-demo-panel" data-contact-demo-panel="1">' +
79      '<p class="contact-demo-intro">Tell us about yourself, then pick a time that works for your team.</p>' +
80      '<form id="contactDemoForm" novalidate>' +
81      '<div class="contact-demo-form-row">' +
82      '<div class="form-group">' +
83      '<label for="contactDemoFirstName">First name</label>' +
84      '<input type="text" id="contactDemoFirstName" autocomplete="given-name" required>' +
85      '<p class="contact-demo-field-error" data-error-for="contactDemoFirstName"></p>' +
86      '</div>' +
87      '<div class="form-group">' +
88      '<label for="contactDemoLastName">Last name</label>' +
89      '<input type="text" id="contactDemoLastName" autocomplete="family-name" required>' +
90      '<p class="contact-demo-field-error" data-error-for="contactDemoLastName"></p>' +
91      '</div>' +
92      '</div>' +
93      '<div class="form-group">' +
94      '<label for="contactDemoEmail">Work email</label>' +
95      '<input type="email" id="contactDemoEmail" autocomplete="email" required>' +
96      '<p class="contact-demo-field-error" data-error-for="contactDemoEmail"></p>' +
97      '</div>' +
98      '<div class="form-group">' +
99      '<label for="contactDemoJobTitle">Job title</label>' +
100      '<input type="text" id="contactDemoJobTitle" autocomplete="organization-title" required>' +
101      '<p class="contact-demo-field-error" data-error-for="contactDemoJobTitle"></p>' +
102      '</div>' +
103      '<div class="form-group">' +
104      '<label for="contactDemoOrganisation">Organisation / Company <span class="contact-demo-optional">(optional)</span></label>' +
105      '<input type="text" id="contactDemoOrganisation" autocomplete="organization">' +
106      '</div>' +
107      '<fieldset class="contact-demo-priorities">' +
108      '<legend>What would you like to prioritise in the demo?</legend>' +
109      buildPriorityCheckboxes() +
110      '<p class="contact-demo-field-error" data-error-for="contact_demo_priorities"></p>' +
111      '</fieldset>' +
112      '<p class="contact-demo-consent">By continuing you consent to the use of this information for scheduling a demo of Hello, It\'s Me® and for related communication. Your data will be handled in accordance with our <a href="/privacy-policy/" target="_blank" rel="noopener noreferrer">privacy policy</a>.</p>' +
113      '<button type="submit" class="btn btn-primary btn-arrow" id="contactDemoSubmit">' +
114      '<span class="contact-demo-submit-spinner" aria-hidden="true"></span>' +
115      '<span class="contact-demo-submit-text">Continue to Book Your Time<span class="arrow-circle">→</span></span>' +
116      '</button>' +
117      '<p class="contact-demo-form-error" id="contactDemoFormError" role="alert"></p>' +
118      '</form>' +
119      '</div>' +
120      '<div class="contact-demo-panel" data-contact-demo-panel="2" hidden>' +
121      '<h3 class="contact-demo-step-title">Pick a time that works.</h3>' +
122      '<p class="contact-demo-intro">Your details have been saved. Choose a slot below to confirm your demo.</p>' +
123      '<div class="contact-demo-calendar-card">' +
124      '<p class="contact-demo-calendar-saved">Your details have been saved.</p>' +
125      '<a href="' +
126      escapeAttr(BOOKINGS_URL) +
127      '" class="btn btn-primary btn-arrow contact-demo-calendar-btn" target="_blank" rel="noopener noreferrer">→ Open Booking Calendar<span class="arrow-circle">→</span></a>' +
128      '<p class="contact-demo-calendar-hint">Opens in a new tab. Come back here once you\'ve picked a time.</p>' +
129      '<button type="button" class="btn btn-primary btn-arrow" id="contactDemoBooked">I\'ve booked my time<span class="arrow-circle">→</span></button>' +
130      '</div>' +
131      '</div>' +
132      '<div class="contact-demo-panel" data-contact-demo-panel="3" hidden>' +
133      '<div class="contact-demo-confirm">' +
134      '<div class="contact-demo-confirm-icon" aria-hidden="true">✓</div>' +
135      '<h3 class="contact-demo-step-title">You\'re all booked!</h3>' +
136      '<p class="contact-demo-confirm-text">Thanks for booking a demo with Hello, It\'s Me®. Our team will be in touch shortly to confirm your time and send the meeting link. If you have any questions in the meantime, email us at [email protected]</p>' +
137      '<div class="contact-demo-confirm-details" id="contactDemoConfirmDetails"></div>' +
138      '</div>' +
139      '</div>' +
140      '</div>'
141    );
142  }
143
144  function q(selector) {
145    return rootEl ? rootEl.querySelector(selector) : null;
146  }
147
148  function setStep(step) {
149    rootEl.querySelectorAll('[data-contact-demo-panel]').forEach((panel) => {
150      const n = Number(panel.getAttribute('data-contact-demo-panel'));
151      panel.hidden = n !== step;
152    });
153  }
154
155  function resetFlow() {
156    savedSubmission = null;
157    setStep(1);
158    const form = q('#contactDemoForm');
159    if (form) {
160      form.reset();
161      clearAllFieldErrors();
162      hideFormError();
163      setSubmitLoading(false);
164    }
165  }
166
167  function clearFieldError(fieldId) {
168    const input = q('#' + fieldId);
169    const err = rootEl.querySelector('[data-error-for="' + fieldId + '"]');
170    if (input) input.classList.remove('is-invalid');
171    if (err) {
172      err.textContent = '';
173      err.classList.remove('is-visible');
174    }
175  }
176
177  function clearAllFieldErrors() {
178    [
179      'contactDemoFirstName',
180      'contactDemoLastName',
181      'contactDemoEmail',
182      'contactDemoJobTitle',
183      'contact_demo_priorities'
184    ].forEach(clearFieldError);
185  }
186
187  function showFieldError(fieldId, message) {
188    const input = q('#' + fieldId);
189    const err = rootEl.querySelector('[data-error-for="' + fieldId + '"]');
190    if (input) input.classList.add('is-invalid');
191    if (err) {
192      err.textContent = message;
193      err.classList.add('is-visible');
194    }
195  }
196
197  function showFormError(message) {
198    const el = q('#contactDemoFormError');
199    if (el) {
200      el.textContent = message;
201      el.classList.add('is-visible');
202    }
203  }
204
205  function hideFormError() {
206    const el = q('#contactDemoFormError');
207    if (el) {
208      el.textContent = '';
209      el.classList.remove('is-visible');
210    }
211  }
212
213  function setSubmitLoading(loading) {
214    const btn = q('#contactDemoSubmit');
215    if (!btn) return;
216    btn.disabled = loading;
217    btn.classList.toggle('is-loading', loading);
218    btn.setAttribute('aria-busy', loading ? 'true' : 'false');
219  }
220
221  function isValidEmail(value) {
222    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
223  }
224
225  function collectFormData() {
226    return {
227      firstName: q('#contactDemoFirstName').value.trim(),
228      lastName: q('#contactDemoLastName').value.trim(),
229      email: q('#contactDemoEmail').value.trim(),
230      jobTitle: q('#contactDemoJobTitle').value.trim(),
231      organisation: q('#contactDemoOrganisation').value.trim(),
232      priorities: Array.from(
233        rootEl.querySelectorAll('input[name="contact_demo_priority"]:checked')
234      ).map((cb) => cb.value)
235    };
236  }
237
238  function validateForm() {
239    clearAllFieldErrors();
240    hideFormError();
241    let valid = true;
242    const data = collectFormData();
243
244    if (!data.firstName) {
245      showFieldError('contactDemoFirstName', 'First name is required.');
246      valid = false;
247    }
248    if (!data.lastName) {
249      showFieldError('contactDemoLastName', 'Last name is required.');
250      valid = false;
251    }
252    if (!data.email) {
253      showFieldError('contactDemoEmail', 'Work email is required.');
254      valid = false;
255    } else if (!isValidEmail(data.email)) {
256      showFieldError('contactDemoEmail', 'Enter a valid email address.');
257      valid = false;
258    }
259    if (!data.jobTitle) {
260      showFieldError('contactDemoJobTitle', 'Job title is required.');
261      valid = false;
262    }
263    if (!data.priorities.length) {
264      showFieldError('contact_demo_priorities', 'Select at least one demo priority.');
265      valid = false;
266    }
267
268    return valid ? data : null;
269  }
270
271  async function saveToSupabase(data) {
272    if (!global.HIMFormSubmit?.submitForm) {
273      throw new Error('Secure form handler not loaded');
274    }
275
276    const payload = {
277      first_name: data.firstName,
278      last_name: data.lastName,
279      email: data.email,
280      job_title: data.jobTitle,
281      organisation: data.organisation || null,
282      demo_priorities: data.priorities,
283      submitted_at: new Date().toISOString(),
284      status: 'pending'
285    };
286
287    const form = q('#contactDemoForm');
288    const result = await global.HIMFormSubmit.submitForm('demo_booking', payload, {
289      form: form
290    });
291
292    return {
293      payload: payload,
294      row: result.record || result.data || result.row || result
295    };
296  }
297
298  function goToStep2() {
299    setStep(2);
300  }
301
302  function goToStep3() {
303    setStep(3);
304    const details = q('#contactDemoConfirmDetails');
305    if (details && savedSubmission) {
306      const org = savedSubmission.organisation || '-';
307      details.innerHTML =
308        '<p><strong>Name:</strong> ' +
309        escapeHtml(savedSubmission.firstName + ' ' + savedSubmission.lastName) +
310        '</p>' +
311        '<p><strong>Email:</strong> ' +
312        escapeHtml(savedSubmission.email) +
313        '</p>' +
314        '<p><strong>Organisation:</strong> ' +
315        escapeHtml(org) +
316        '</p>';
317    }
318  }
319
320  async function handleFormSubmit(e) {
321    e.preventDefault();
322    const data = validateForm();
323    if (!data) return;
324
325    setSubmitLoading(true);
326    hideFormError();
327
328    try {
329      await ensureSupabaseEnv();
330      const result = await saveToSupabase(data);
331      savedSubmission = {
332        firstName: data.firstName,
333        lastName: data.lastName,
334        email: data.email,
335        organisation: data.organisation
336      };
337      goToStep2();
338    } catch (err) {
339      console.error('[ContactDemo]', err);
340      showFormError(SAVE_ERROR_MSG);
341    } finally {
342      setSubmitLoading(false);
343    }
344  }
345
346  function attachEvents() {
347    const form = q('#contactDemoForm');
348    if (form && global.HIMFormProtection?.attachToForm) {
349      global.HIMFormProtection.attachToForm(form);
350    }
351    form?.addEventListener('submit', handleFormSubmit);
352    q('#contactDemoBooked')?.addEventListener('click', goToStep3);
353
354    rootEl.querySelectorAll('input').forEach((input) => {
355      input.addEventListener('input', () => {
356        const id = input.id;
357        if (id) clearFieldError(id);
358        if (input.name === 'contact_demo_priority') clearFieldError('contact_demo_priorities');
359      });
360    });
361  }
362
363  function ensureMounted(formCard) {
364    if (!formCard) return;
365    hostEl = formCard;
366    if (!rootEl) {
367      formCard.insertAdjacentHTML('beforeend', buildMarkup());
368      rootEl = document.getElementById('contactDemoInline');
369      attachEvents();
370    }
371  }
372
373  function show(formCard) {
374    ensureMounted(formCard);
375    const contactForm = document.getElementById('contactForm');
376    if (contactForm) contactForm.hidden = true;
377    if (rootEl) {
378      rootEl.hidden = false;
379      resetFlow();
380    }
381  }
382
383  function hide() {
384    if (rootEl) rootEl.hidden = true;
385    const contactForm = document.getElementById('contactForm');
386    if (contactForm) contactForm.hidden = false;
387  }
388
389  function isContactPage() {
390    const path = global.location.pathname || '';
391    return path.includes('contact');
392  }
393
394  global.HIMContactDemoInline = {
395    show,
396    hide,
397    isContactPage
398  };
399})(window);

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.