1(() => { 2 const KEY_ESCAPE = 'Escape'; 3 const SUCCESS_MODAL_DELAY = 3000; 4 const STATUS = { ERROR: 0, SUCCESS: 1 }; 5 const CAPTCHA_UNAVAILABLE_TEXT = 'Verification could not be loaded. Please reload the page and try again.'; 6 7 const body = document.querySelector('body'); 8 const openButtons = document.querySelectorAll('.js-gq-modal-form-open'); 9 const overlay = document.querySelector('.js-gq-overlay'); 10 const formModal = document.querySelector('.js-gq-modal-form'); 11 const successModal = document.querySelector('.js-gq-modal-success'); 12 const errorModal = document.querySelector('.js-gq-modal-error'); 13 14 if (openButtons.length === 0 || !overlay || !formModal || !successModal || !errorModal) { 15 console.error("Don't exist modals or button open modal"); 16 return; 17 } 18 19 const formCloseButton = formModal.querySelector('.js-gq-modal-form-close'); 20 const errorTitle = errorModal.querySelector('.js-gq-modal-error-title'); 21 const successCloseButton = successModal.querySelector('.js-gq-modal-success-close'); 22 23 if (!formCloseButton || !errorTitle || !successCloseButton) { 24 console.error("Don't exist button close modal or title modal node"); 25 return; 26 } 27 28 const form = formModal.querySelector('.js-gq-form'); 29 const submitButton = formModal.querySelector('.js-gq-form-submit'); 30 const numberInput = formModal.querySelector('.js-gq-num'); 31 const rangeInput = formModal.querySelector('.js-gq-range'); 32 const phoneInput = formModal.querySelector('.js-gq-phone'); 33 const citiesBlock = formModal.querySelector('.js-gq-cities'); 34 const daysBlock = formModal.querySelector('.js-gq-days'); 35 const captchaBlock = formModal.querySelector('.js-gq-captcha'); 36 37 if (!form || !submitButton || !numberInput || !rangeInput || !phoneInput || !citiesBlock || !daysBlock || !captchaBlock) { 38 console.error('Form of modal is invalid'); 39 return; 40 } 41 42 if (typeof IMask === 'undefined') { 43 console.error("IMask lib don't exist"); 44 return; 45 } 46 47 const formAction = form.action; 48 if (!formAction || formAction.length === 0) { 49 console.error('Form action is invalid'); 50 return; 51 } 52 53 const phoneMask = IMask(phoneInput, { 54 mask: '+{1} (000) 000-0000', 55 prepare: (value) => value.replace(/\D/g, ''), 56 commit: (value, masked) => { 57 masked._value = value.replace(/\D/g, ''); 58 }, 59 }); 60 61 // Called by reCAPTCHA (data-callback) once the checkbox is solved. 62 window.gqRecaptchaCallback = () => captchaBlock.classList.remove('error'); 63 64 function isCaptchaReady() { 65 return typeof window.grecaptcha !== 'undefined' && typeof window.grecaptcha.getResponse === 'function'; 66 } 67 68 function getCaptchaToken() { 69 if (!isCaptchaReady()) { 70 return ''; 71 } 72 73 try { 74 return window.grecaptcha.getResponse() || ''; 75 } catch (error) { 76 // Thrown while the widget has not been rendered yet. 77 return ''; 78 } 79 } 80 81 function resetCaptcha() { 82 if (!isCaptchaReady()) { 83 return; 84 } 85 86 try { 87 window.grecaptcha.reset(); 88 } catch (error) { 89 console.error(error); 90 } 91 } 92 93 function validateDeliveries() { 94 if (+form['daily-deliveries'].value === 0) { 95 daysBlock.classList.add('error'); 96 return false; 97 } 98 99 daysBlock.classList.remove('error'); 100 return true; 101 } 102 103 function validateRegions() { 104 const anyChecked = form['gq-region-cn'].checked 105 || form['gq-region-la'].checked 106 || form['gq-region-ma'].checked 107 || form['gq-region-nj'].checked 108 || form['gq-region-ny'].checked 109 || form['gq-region-pe'].checked; 110 111 citiesBlock.classList.toggle('error', !anyChecked); 112 return anyChecked; 113 } 114 115 function validateFields() { 116 return [form['gq-name'], form['gq-business-name'], form['gq-phone'], form['gq-email']] 117 .map((field) => { 118 const filled = field.value.length > 0; 119 field.classList.toggle('error', !filled); 120 return filled; 121 }) 122 .every((filled) => filled); 123 } 124 125 function validateCaptcha() { 126 const passed = getCaptchaToken().length > 0; 127 captchaBlock.classList.toggle('error', !passed); 128 return passed; 129 } 130 131 async function sendQuote(quote) { 132 try { 133 const response = await fetch(formAction, { 134 method: 'POST', 135 headers: { 136 Accept: 'application/json', 137 'Content-Type': 'application/json', 138 }, 139 body: JSON.stringify({ 140 quote, 141 captchaToken: getCaptchaToken(), 142 'gq-website': form['gq-website'] ? form['gq-website'].value : '', 143 }), 144 }); 145 146 const data = await response.json(); 147 148 closeModal(formModal); 149 150 if (data && data.status === STATUS.ERROR) { 151 showErrorModal(data.payload && data.payload.text); 152 } 153 154 if (data && data.status === STATUS.SUCCESS) { 155 openModal(successModal); 156 setTimeout(() => closeModal(successModal), SUCCESS_MODAL_DELAY); 157 } 158 } catch (error) { 159 console.error(error); 160 showErrorModal(); 161 } 162 } 163 164 function openModal(modal) { 165 closeAllModals(); 166 body.classList.add('scroll-lock'); 167 overlay.classList.add('show'); 168 modal.classList.add('show'); 169 document.addEventListener('keydown', handleKeydown); 170 } 171 172 function closeModal(modal) { 173 form.reset(); 174 overlay.classList.remove('show'); 175 modal.classList.remove('show'); 176 body.classList.remove('scroll-lock'); 177 document.removeEventListener('keydown', handleKeydown); 178 } 179 180 function closeAllModals() {
181 [formModal, errorModal, successModal].forEach((modal) => closeModal(modal)); 182 } 183 184 function showErrorModal(text) { 185 openModal(errorModal); 186 errorTitle.textContent = text || errorTitle.dataset.text; 187 } 188 189 function openFormModal() { 190 openModal(formModal); 191 captchaBlock.classList.remove('error'); 192 resetCaptcha(); 193 } 194 195 function handleKeydown(event) { 196 if (event.key === KEY_ESCAPE) { 197 closeAllModals(); 198 } 199 } 200 201 openButtons.forEach((button) => button.addEventListener('click', openFormModal)); 202 203 rangeInput.addEventListener('input', (event) => { 204 numberInput.value = event.target.value; 205 }); 206 207 numberInput.addEventListener('input', (event) => { 208 const value = +event.target.value; 209 210 if (isNaN(value)) { 211 event.target.value = rangeInput.value; 212 return; 213 } 214 215 if (value < rangeInput.min) { 216 event.target.value = rangeInput.min; 217 } 218 219 if (value > rangeInput.max) { 220 event.target.value = rangeInput.max; 221 } 222 223 rangeInput.value = event.target.value; 224 }); 225 226 form.addEventListener('submit', async (event) => { 227 event.preventDefault(); 228 229 const deliveriesValid = validateDeliveries(); 230 const regionsValid = validateRegions(); 231 const fieldsValid = validateFields(); 232 233 if (!deliveriesValid || !regionsValid || !fieldsValid) { 234 return; 235 } 236 237 if (!isCaptchaReady()) { 238 showErrorModal(CAPTCHA_UNAVAILABLE_TEXT); 239 return; 240 } 241 242 if (!validateCaptcha()) { 243 return; 244 } 245 246 submitButton.classList.add('loading'); 247 submitButton.setAttribute('disabled', 'disabled'); 248 249 await sendQuote({ 250 dailyDeliveries: +form['daily-deliveries'].value, 251 regionCn: form['gq-region-cn'].checked, 252 regionNj: form['gq-region-nj'].checked, 253 regionLa: form['gq-region-la'].checked, 254 regionPe: form['gq-region-pe'].checked, 255 regionNy: form['gq-region-ny'].checked, 256 regionMa: form['gq-region-ma'].checked, 257 name: form['gq-name'].value, 258 businessName: form['gq-business-name'].value, 259 email: form['gq-email'].value, 260 phone: phoneMask.unmaskedValue.slice(1, 11), 261 }); 262 263 // reCAPTCHA responses are single use, so a new one is needed for the next attempt. 264 resetCaptcha(); 265 266 submitButton.classList.remove('loading'); 267 submitButton.removeAttribute('disabled'); 268 }); 269 270 overlay.addEventListener('click', (event) => { 271 if (event.target === overlay) { 272 closeAllModals(); 273 } 274 }); 275 276 formCloseButton.addEventListener('click', () => closeModal(formModal)); 277 successCloseButton.addEventListener('click', () => closeModal(successModal)); 278})();
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.