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.