1import { Controller } from '../stimulus.js' 2import { postJson, EMAIL_REGEX } from '../utils/fetchUtils.js' 3const ERROR_GENERIC = 'An error occurred. Please try again later.'; 4const ERROR_RESEND = 'Could not resend the activation email. Please try again later.'; 5 6export default class extends Controller { 7 static targets = [ 8 'emailInput', 'errorMessage', 'fullnameInput', 'timestampInput', 9 'formSection', 'checkEmailSection', 'welcomeBackSection', 10 'confirmedEmail', 'welcomeEmail', 'submitButton', 'retryMessage' 11 ]; 12 13 #pendingEmail = null; 14 #retryCooldownTimer = null; 15 #countdownSpan = null; 16 #tryAgainLink = null; 17 #isRetrying = false; 18 19 disconnect() { 20 this.#clearCooldown(); 21 } 22 23 async submit(event) { 24 event.preventDefault(); 25 26 const email = this.emailInputTarget.value.trim(); 27 this.#clearMessages(); 28 29 if (!EMAIL_REGEX.test(email)) { 30 this.errorMessageTarget.innerText = 'Please enter a valid email address'; 31 return; 32 } 33 34 await this.#submitEmail(email); 35 } 36 37 async tryAgain(event) { 38 event.preventDefault(); 39 if (this.#retryCooldownTimer || this.#isRetrying) return; 40 41 const link = event.currentTarget; 42 this.#isRetrying = true; 43 this.#clearMessages(); 44 45 const email = this.#pendingEmail; 46 if (!email) { this.#isRetrying = false; return; } 47 48 let message = ERROR_RESEND; 49 try { 50 const response = await this.#requestSignup(email); 51 if (response.ok || response.status === 409) message = 'Activation email re-sent!'; 52 } catch { } 53 this.retryMessageTarget.innerText = message; 54 this.#isRetrying = false; 55 56 this.#startRetryCooldown(link); 57 } 58 59 #startRetryCooldown(link, seconds = 60) { 60 this.#tryAgainLink = link; 61 let remaining = seconds; 62 63 this.#countdownSpan = document.createElement('span'); 64 const countdown = this.#countdownSpan; 65 link.insertAdjacentElement('afterend', countdown); 66 link.classList.add('pointer-events-none', 'opacity-50'); 67 68 const update = () => { countdown.innerText = ` in ${remaining}s`; }; 69 update(); 70 71 this.#retryCooldownTimer = setInterval(() => { 72 remaining -= 1; 73 update(); 74 75 if (remaining <= 0) this.#clearCooldown(); 76 }, 1000); 77 } 78 79 #clearCooldown() { 80 if (this.#retryCooldownTimer) { 81 clearInterval(this.#retryCooldownTimer); 82 this.#retryCooldownTimer = null; 83 } 84 this.#countdownSpan?.remove(); 85 this.#countdownSpan = null; 86 if (this.#tryAgainLink) { 87 this.#tryAgainLink.classList.remove('pointer-events-none', 'opacity-50'); 88 this.#tryAgainLink = null; 89 } 90 } 91 92 async #submitEmail(email) { 93 this.#setSubmitting(true); 94 95 try { 96 const response = await this.#requestSignup(email); 97 98 if (response.ok) { 99 this.#showCheckEmail(email); 100 return; 101 } 102 103 if (response.status === 409) { 104 this.#showWelcomeBack(email); 105 return; 106 } 107 108 this.errorMessageTarget.innerText = response.status === 422 109 ? 'Please enter a valid email address' 110 : ERROR_GENERIC; 111 } catch { 112 this.errorMessageTarget.innerText = ERROR_GENERIC; 113 } finally { 114 this.#setSubmitting(false); 115 } 116 } 117 118 #requestSignup(email) { 119 return postJson('/alerts', { 120 email, 121 fullname: this.fullnameInputTarget.value, 122 timestamp: this.timestampInputTarget.value, 123 }); 124 } 125 126 #setSubmitting(isSubmitting) { 127 this.submitButtonTarget.disabled = isSubmitting; 128 } 129 130 #clearMessages() { 131 this.errorMessageTarget.innerText = ''; 132 this.retryMessageTarget.innerText = ''; 133 } 134 135 #showSection(section) { 136 [this.formSectionTarget, this.checkEmailSectionTarget, this.welcomeBackSectionTarget] 137 .forEach(s => s.classList.toggle('hidden', s !== section)); 138 } 139 140 #showCheckEmail(email) { 141 this.#pendingEmail = email; 142 this.confirmedEmailTarget.innerText = email; 143 this.#showSection(this.checkEmailSectionTarget); 144 } 145 146 #showWelcomeBack(email) { 147 this.welcomeEmailTarget.innerText = email; 148 this.#showSection(this.welcomeBackSectionTarget); 149 } 150}
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.