1import {Controller} from '@hotwired/stimulus' 2 3const POSTAL_CODE_LENGTH = 5; 4const FULL_PAGE_HASH = '#assistantIAfullPage'; 5 6export default class extends Controller { 7 static targets = ['postalCode', 'postalCodeError', 'contactName', 'provinceCta', 'countryCta', 'countrySelect'] 8 static values = { 9 contactNameUrl: String, 10 franceId: String, 11 } 12 13 connect() { 14 this.updateProvinceCta(''); 15 if (this.hasCountrySelectTarget) { 16 this.selectCountry(); 17 } 18 19 // Le bouton d'agrandissement rendu par l'app CDN appelle cette fonction globale. 20 window.toggleChatbotExpand = (forceExpand = null) => this.toggleChatbotExpand(forceExpand); 21 22 if (window.location.hash === FULL_PAGE_HASH) { 23 this.waitForChatbot(() => this.toggleChatbotExpand(true)); 24 } 25 } 26 27 disconnect() { 28 if (this.chatbotWait) { 29 clearInterval(this.chatbotWait); 30 } 31 delete window.toggleChatbotExpand; 32 } 33 34 lookupContact() { 35 const postalCode = this.postalCodeTarget.value.trim(); 36 37 this.updateProvinceCta(postalCode); 38 39 if (postalCode.length !== POSTAL_CODE_LENGTH) { 40 this.contactNameTarget.textContent = ''; 41 42 return; 43 } 44 45 fetch(`${this.contactNameUrlValue}?postalCode=${encodeURIComponent(postalCode)}`, { 46 headers: {Accept: 'application/json'}, 47 }) 48 .then((response) => (response.ok ? response.text() : '')) 49 .then((body) => { 50 this.contactNameTarget.textContent = this.readContactName(body); 51 this.postalCodeErrorTarget.textContent = ''; 52 }) 53 .catch(() => { 54 this.contactNameTarget.textContent = ''; 55 }); 56 } 57 58 /** Tolère une réponse JSON ({name|contactName|...}) ou du texte brut « Prénom Nom ». */ 59 readContactName(body) { 60 if (!body) { 61 return ''; 62 } 63 64 try { 65 const parsed = JSON.parse(body); 66 67 if (typeof parsed === 'string') { 68 return parsed; 69 } 70 71 return parsed?.name ?? parsed?.contactName ?? parsed?.fullName 72 ?? [parsed?.firstName, parsed?.lastName].filter(Boolean).join(' '); 73 } catch { 74 return body.trim(); 75 } 76 } 77 78 /** 79 * L'ouverture passe par ce contrôleur plutôt que par une seconde action `popin#open` : 80 * Stimulus déclenche les actions dans l'ordre de connexion des contrôleurs, pas dans 81 * celui de l'attribut `data-action`, donc la validation ne pourrait pas la précéder. 82 */ 83 openPopin(event) { 84 this.applyPopinParams(event); 85 this.popinController?.open(); 86 } 87 88 openProvincePopin(event) { 89 if (this.postalCodeTarget.value.trim().length !== POSTAL_CODE_LENGTH) { 90 this.postalCodeErrorTarget.textContent = this.postalCodeErrorTarget.dataset.message 91 || 'Code postal invalide !'; 92 93 return; 94 } 95 96 this.postalCodeErrorTarget.textContent = ''; 97 this.openPopin(event); 98 } 99 100 get popinController() { 101 return this.application.getControllerForElementAndIdentifier(this.element, 'popin'); 102 } 103 104 selectCountry() { 105 const country = this.countrySelectTarget.value; 106 const params = new URLSearchParams(); 107 108 if (country !== '') { 109 params.set('country', country); 110 } 111 112 this.setCtaParams(this.countryCtaTarget, params); 113 } 114 115 updateProvinceCta(postalCode) { 116 if (!this.hasProvinceCtaTarget) { 117 return; 118 } 119 120 const params = new URLSearchParams({country: this.franceIdValue}); 121 122 if (postalCode !== '') { 123 params.set('postalCode', postalCode); 124 } 125 126 this.setCtaParams(this.provinceCtaTarget, params); 127 } 128 129 /** 130 * Les CTA ouvrent la popin ; on garde les paramètres sur le lien pour que la popin les 131 * transmette au formulaire de demande d'informations. 132 */ 133 setCtaParams(cta, params) { 134 if (!cta) { 135 return; 136 } 137 138 const query = params.toString(); 139 cta.dataset.switchPopinParams = query; 140 cta.setAttribute('href', query === '' ? '#' : `#${query}`); 141 } 142 143 /** 144 * Recopie les paramètres du CTA cliqué (country / postalCode) sur les deux liens de la 145 * popin, afin que la zone du chargé d'affaire suive l'utilisateur dans le formulaire. 146 */ 147 applyPopinParams(event) { 148 const popin = document.getElementById(this.element.dataset.popinModalIdValue); 149 150 if (!popin) { 151 return; 152 } 153 154 const extra = new URLSearchParams(event.currentTarget.dataset.switchPopinParams || ''); 155 156 for (const cta of popin.querySelectorAll('[data-switch-popin-target]')) { 157 cta.dataset.baseHref = cta.dataset.baseHref || cta.getAttribute('href'); 158 159 const url = new URL(cta.dataset.baseHref, window.location.origin); 160 161 for (const [name, value] of extra) { 162 url.searchParams.set(name, value); 163 } 164 165 cta.setAttribute('href', url.pathname + (url.search === '' ? '' : url.search)); 166 } 167 } 168 169 toggleChatbotExpand(forceExpand = null) { 170 const header = document.querySelector('#chatbot .block-header .block-right .fa-minus-close'); 171 const icon = header?.querySelector('.expand, .compress'); 172 173 if (!icon) { 174 return; 175 } 176 177 const isExpand = forceExpand !== null ? forceExpand : icon.classList.contains('expand'); 178 179 icon.classList.toggle('fa-expand'); 180 icon.classList.toggle('fa-compress'); 181 icon.classList.toggle('expand'); 182 icon.classList.toggle('compress'); 183 184 if (typeof window.callChatbotDl === 'function') { 185 window.callChatbotDl({event: 'expand_cb'}); 186 } 187 188 const contactOptions = document.getElementById('contact-options'); 189 190 if (isExpand) { 191 const top = contactOptions?.getBoundingClientRect().top ?? 0; 192 window.scrollTo({top: window.scrollY + top - 58}); 193 } 194 195 contactOptions?.classList.toggle('hide', isExpand); 196 } 197 198 /** L'app chatbot est montée par un bundle CDN : on attend que son en-tête existe. */ 199 waitForChatbot(callback, attempts = 40) { 200 let remaining = attempts; 201 202 this.chatbotWait = setInterval(() => { 203 const icon = document.querySelector('#chatbot .block-header .block-right .fa-minus-close .expand'); 204 205 if (icon || remaining-- <= 0) { 206 clearInterval(this.chatbotWait); 207 this.chatbotWait = null; 208 209 if (icon) { 210 callback(); 211 } 212 } 213 }, 250); 214 } 215}
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.