1import dialogController from "./dialog.js"; 2import { postJson, EMAIL_REGEX } from "../utils/fetchUtils.js"; 3 4const COOKIE_NAME = 'alert-dialog-suppressed'; 5const AUTO_SHOW_DELAY_MS = 5000; 6const ERROR_GENERIC = 'An error occurred. Please try again later.'; 7 8window.AlertDialog = { 9 controller: null, 10 openDialog: function () { 11 if (this.controller) { 12 this.controller.open(); 13 } else { 14 console.error("Alert dialog controller is not initialized"); 15 } 16 } 17}; 18 19export default class extends dialogController { 20 static targets = [ 21 "emailInput", "userEmail", "errorMessage", 22 "subscribeForm", "checkEmail", "welcomeBack", "welcomeBackEmail", 23 "activated", "alreadyActivated", "activateErrorMessage", 24 "deactivated", "alreadyDeactivated", "deactivateErrorMessage" 25 ]; 26 27 #submittedSuccessfully = false; 28 #openedFromQueryParam = false; 29 #autoShowTimer = null; 30 #autoShowIdleId = null; 31 #closeListener = null; 32 33 open() { 34 this.emailInputTargets.forEach(target => target.value = ""); 35 this.errorMessageTargets.forEach(target => target.innerText = ""); 36 this.subscribeFormTarget.classList.remove("hidden"); 37 this.checkEmailTarget.classList.add("hidden"); 38 this.welcomeBackTarget.classList.add("hidden"); 39 this.hideAllStatusMessages(); 40 this.modalTarget.show(); 41 } 42 43 connect() { 44 window.AlertDialog.controller = this; 45 46 this.#closeListener = this.#onModalClose.bind(this); 47 this.modalTarget.addEventListener("close", this.#closeListener); 48 49 this.#openedFromQueryParam = this.#showMessageFromQueryParam(); 50 51 if (!this.#openedFromQueryParam && !this.#hasSuppressCookie() && !this.#isPhone()) { 52 this.#autoShowTimer = setTimeout(() => this.#openWhenIdle(), AUTO_SHOW_DELAY_MS); 53 } 54 } 55 56 disconnect() { 57 if (window.AlertDialog.controller === this) { 58 window.AlertDialog.controller = null; 59 } 60 61 if (this.#closeListener) { 62 this.modalTarget.removeEventListener("close", this.#closeListener); 63 this.#closeListener = null; 64 } 65 66 if (this.#autoShowTimer) { 67 clearTimeout(this.#autoShowTimer); 68 this.#autoShowTimer = null; 69 } 70 71 if (this.#autoShowIdleId && "cancelIdleCallback" in window) { 72 cancelIdleCallback(this.#autoShowIdleId); 73 this.#autoShowIdleId = null; 74 } 75 } 76 77 #openWhenIdle() { 78 if ("requestIdleCallback" in window) { 79 this.#autoShowIdleId = requestIdleCallback(() => this.open(), { timeout: 2000 }); 80 } else { 81 this.open(); 82 } 83 } 84 85 #onModalClose() { 86 this.enableBodyScroll(); 87 88 if (this.#autoShowTimer) { 89 clearTimeout(this.#autoShowTimer); 90 this.#autoShowTimer = null; 91 } 92 93 if (this.#openedFromQueryParam) return; 94 95 if (this.#submittedSuccessfully) { 96 this.#suppressIndefinitely(); 97 } else { 98 this.#suppressFor24h(); 99 } 100 } 101 102 #isPhone() { 103 return window.matchMedia("(max-width: 767px)").matches; 104 } 105 106 #hasSuppressCookie() { 107 return document.cookie.split(';').some(c => c.trim().startsWith(`${COOKIE_NAME}=`)); 108 } 109 110 #suppressFor24h() { 111 const expires = new Date(Date.now() + 24 * 60 * 60 * 1000).toUTCString(); 112 document.cookie = `${COOKIE_NAME}=1; expires=${expires}; path=/; SameSite=Lax`; 113 } 114 115 #suppressIndefinitely() { 116 const expires = new Date('2099-01-01').toUTCString(); 117 document.cookie = `${COOKIE_NAME}=1; expires=${expires}; path=/; SameSite=Lax`; 118 } 119 120 #showMessageFromQueryParam() { 121 const urlParams = new URLSearchParams(window.location.search); 122 const status = urlParams.get("alert-status"); 123 124 const statusElement = status && { 125 "activated": this.activatedTarget, 126 "already_activated": this.alreadyActivatedTarget, 127 "activation_error": this.activateErrorMessageTarget, 128 "deactivated": this.deactivatedTarget, 129 "already_deactivated": this.alreadyDeactivatedTarget, 130 "deactivation_error": this.deactivateErrorMessageTarget 131 }[status]; 132 133 if (statusElement) { 134 this.open(); 135 this.subscribeFormTarget.classList.add("hidden"); 136 this.hideAllStatusMessages(); 137 statusElement.classList.remove("hidden"); 138 return true; 139 } 140 141 return false;
142 } 143 144 hideAllStatusMessages() { 145 [ 146 this.activatedTarget, 147 this.alreadyActivatedTarget, 148 this.activateErrorMessageTarget, 149 this.deactivatedTarget, 150 this.alreadyDeactivatedTarget, 151 this.deactivateErrorMessageTarget 152 ].forEach(target => target.classList.add("hidden")); 153 } 154 155 async submit(event) { 156 event.preventDefault(); 157 158 const form = event.target; 159 const emailInput = form.querySelector('[data-alert-dialog-target="emailInput"]'); 160 const errorMessage = form.querySelector('[data-alert-dialog-target="errorMessage"]'); 161 const fullnameInput = form.querySelector('[data-alert-dialog-target="fullnameInput"]'); 162 const timestampInput = form.querySelector('[data-alert-dialog-target="timestampInput"]'); 163 const submitButton = form.querySelector('button[type="submit"]'); 164 165 errorMessage.innerText = ""; 166 167 const email = emailInput.value.trim(); 168 169 if (!EMAIL_REGEX.test(email)) { 170 errorMessage.innerText = "Please enter a valid email address"; 171 return; 172 } 173 174 submitButton.disabled = true; 175 176 try { 177 const response = await postJson('/alerts', { 178 email, 179 fullname: fullnameInput.value, 180 timestamp: timestampInput.value, 181 }); 182 183 if (response.ok) { 184 this.#showCheckEmail(email); 185 return; 186 } 187 188 if (response.status === 409) { 189 this.#showWelcomeBack(email); 190 return; 191 } 192 193 errorMessage.innerText = response.status === 422 194 ? "Please enter a valid email address" 195 : ERROR_GENERIC; 196 } catch { 197 errorMessage.innerText = ERROR_GENERIC; 198 } finally { 199 submitButton.disabled = false; 200 } 201 } 202 203 #showCheckEmail(email) { 204 this.#submittedSuccessfully = true; 205 this.subscribeFormTargets.forEach(target => target.classList.add('hidden')); 206 this.userEmailTarget.innerText = email; 207 this.checkEmailTarget.classList.remove('hidden'); 208 } 209 210 #showWelcomeBack(email) { 211 this.subscribeFormTargets.forEach(target => target.classList.add('hidden')); 212 this.welcomeBackEmailTarget.innerText = email; 213 this.welcomeBackTarget.classList.remove('hidden'); 214 } 215 216 showForm(event) { 217 event.preventDefault(); 218 this.subscribeFormTarget.classList.remove('hidden'); 219 this.checkEmailTarget.classList.add('hidden'); 220 } 221}
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.