1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static targets = [ 5 "profileOption", 6 "nextButton", 7 "profileSection", 8 "formSection", 9 "roleInput", 10 "formTitle", 11 "authMethodSection", 12 "authMethodInput", 13 "authTab", 14 "emailFields", 15 "phoneFields", 16 "phoneInput", 17 "prenomInput", 18 "nomInput", 19 "emailInput", 20 "passwordInput", 21 "passwordConfirmationInput", 22 "toggleButton", 23 "toggleConfirmationButton", 24 "submitButton", 25 "submitText", 26 "cguCheckbox", 27 "loginFooter" 28 ] 29 30 connect() { 31 this.selectedRole = null 32 this.passwordVisible = false 33 this.passwordConfirmationVisible = false 34 35 // Si un rôle est pré-rempli (depuis une invitation de cohorte), afficher directement le formulaire 36 if (this.hasRoleInputTarget) { 37 const roleInput = this.roleInputTarget 38 if (roleInput && roleInput.value && roleInput.value.trim() !== '') { 39 this.selectedRole = roleInput.value 40 this.showForm() 41 } 42 } 43 } 44 45 // Méthodes pour la sélection de profil 46 selectProfile(event) { 47 this.profileOptionTargets.forEach(option => { 48 option.classList.remove("selected") 49 }) 50 51 event.currentTarget.classList.add("selected") 52 this.selectedRole = event.currentTarget.dataset.role 53 } 54 55 selectAndProceed(event) { 56 this.selectProfile(event) 57 this.proceedToForm(event) 58 } 59 60 updateNextButton() {} 61 62 proceedToForm(event) { 63 event.preventDefault() 64 if (this.selectedRole) { 65 // Si c'est une structure d'accompagnement, rediriger vers la page d'inscription spécifique 66 if (this.selectedRole === 'structure_accompagnement') { 67 window.location.href = '/inscription-structure' 68 return 69 } 70 // Sinon, afficher le formulaire standard 71 this.showForm() 72 } 73 } 74 75 showForm() { 76 if (this.hasProfileSectionTarget) { 77 this.profileSectionTarget.style.display = "none" 78 } 79 if (this.hasFormSectionTarget) { 80 this.formSectionTarget.style.display = "block" 81 } 82 if (this.hasLoginFooterTarget) { 83 this.loginFooterTarget.style.display = "none" 84 } 85 86 // Définir le rôle dans le formulaire (critique pour la soumission) 87 if (this.hasRoleInputTarget) { 88 this.roleInputTarget.value = this.selectedRole 89 } 90 91 let roleDisplayName = "" 92 switch (this.selectedRole) { 93 case 'entrepreneur': 94 roleDisplayName = 'Entrepreneur' 95 break 96 case 'structure_accompagnement': 97 roleDisplayName = "Structure d'accompagnement" 98 break 99 case 'agent_expert': 100 roleDisplayName = 'Agent / Expert' 101 break 102 } 103 if (this.hasFormTitleTarget) { 104 this.formTitleTarget.textContent = `Inscription ${roleDisplayName}` 105 } 106 107 // Switch Mail/Téléphone visible uniquement pour les entrepreneurs (masqué si email imposé par invitation) 108 if (this.hasAuthMethodSectionTarget) { 109 const emailDisabled = this.authMethodSectionTarget.dataset.emailDisabled === 'true' 110 this.authMethodSectionTarget.style.display = (this.selectedRole === 'entrepreneur' && !emailDisabled) ? 'block' : 'none' 111 } 112 if (this.hasAuthMethodInputTarget) { 113 this.authMethodInputTarget.value = 'email' 114 } 115 if (this.hasEmailFieldsTarget) this.emailFieldsTarget.style.display = 'block' 116 if (this.hasPhoneFieldsTarget) this.phoneFieldsTarget.style.display = 'none' 117 if (this.hasAuthTabTarget) { 118 this.authTabTargets.forEach(tab => tab.classList.toggle('active', tab.dataset.authMethod === 'email')) 119 } 120 121 // Valider le formulaire au chargement après un court délai pour s'assurer que les éléments sont visibles 122 setTimeout(() => { 123 this.validateForm() 124 }, 100) 125 } 126 127 backToProfileSelection(event) { 128 event.preventDefault() 129 this.showProfileSelection() 130 } 131 132 switchAuthMethod(event) { 133 const button = event.currentTarget 134 const method = button.dataset.authMethod || 'email' 135 136 if (this.hasAuthTabTarget) { 137 this.authTabTargets.forEach(tab => tab.classList.toggle('active', tab === button)) 138 } 139 if (this.hasAuthMethodInputTarget) { 140 this.authMethodInputTarget.value = method 141 } 142 if (this.hasEmailFieldsTarget && this.hasPhoneFieldsTarget) { 143 if (method === 'phone') { 144 this.emailFieldsTarget.style.display = 'none' 145 this.phoneFieldsTarget.style.display = 'block' 146 } else { 147 this.emailFieldsTarget.style.display = 'block' 148 this.phoneFieldsTarget.style.display = 'none' 149 } 150 } 151 this.validateForm() 152 } 153 154 showProfileSelection() { 155 this.profileSectionTarget.style.display = "block" 156 this.formSectionTarget.style.display = "none" 157 this.loginFooterTarget.style.display = "block" 158 this.selectedRole = null
159 this.profileOptionTargets.forEach(option => option.classList.remove("selected")) 160 this.updateNextButton() 161 } 162 163 // Méthodes pour le formulaire 164 togglePassword(event) { 165 event.preventDefault() 166 this.passwordVisible = !this.passwordVisible 167 168 if (this.passwordVisible) { 169 this.passwordInputTarget.type = "text" 170 this.toggleButtonTarget.innerHTML = '<i class="fa-solid fa-eye-slash"></i>' 171 } else { 172 this.passwordInputTarget.type = "password" 173 this.toggleButtonTarget.innerHTML = '<i class="fa-solid fa-eye"></i>' 174 } 175 } 176 177 togglePasswordConfirmation(event) { 178 event.preventDefault() 179 this.passwordConfirmationVisible = !this.passwordConfirmationVisible 180 181 if (this.passwordConfirmationVisible) { 182 this.passwordConfirmationInputTarget.type = "text" 183 this.toggleConfirmationButtonTarget.innerHTML = '<i class="fa-solid fa-eye-slash"></i>' 184 } else { 185 this.passwordConfirmationInputTarget.type = "password" 186 this.toggleConfirmationButtonTarget.innerHTML = '<i class="fa-solid fa-eye"></i>' 187 } 188 } 189 190 submitForm(event) { 191 const submitButton = this.submitButtonTarget 192 const submitText = this.submitTextTarget 193 const form = event.currentTarget 194 const passwordInput = this.passwordInputTarget 195 const passwordConfirmationInput = this.passwordConfirmationInputTarget 196 const cguCheckbox = this.cguCheckboxTarget 197 const roleInput = this.roleInputTarget 198 const authMethod = this.hasAuthMethodInputTarget ? this.authMethodInputTarget.value : 'email' 199 200 // Validation côté client 201 let hasError = false 202 203 if (!roleInput.value) { 204 hasError = true 205 this.backToProfileSelection(event) 206 return 207 } 208 209 if (authMethod === 'phone') { 210 const phoneInput = form.querySelector('input[name="phone"]') 211 if (phoneInput && !phoneInput.value.trim()) { 212 this.showFieldError(phoneInput, 'Le numéro de téléphone est requis') 213 hasError = true 214 } else if (phoneInput) { 215 this.clearFieldError(phoneInput) 216 } 217 } else { 218 if (this.hasEmailInputTarget && !this.emailInputTarget.value.trim()) { 219 this.showFieldError(this.emailInputTarget, "L'email est requis") 220 hasError = true 221 } else if (this.hasEmailInputTarget) { 222 this.clearFieldError(this.emailInputTarget) 223 } 224 225 if (!passwordInput.value.trim()) { 226 this.showFieldError(passwordInput, "Le mot de passe est requis") 227 hasError = true 228 } else { 229 this.clearFieldError(passwordInput) 230 } 231 232 if (!passwordConfirmationInput.value.trim()) { 233 this.showFieldError(passwordConfirmationInput, "La confirmation du mot de passe est requise") 234 hasError = true 235 } else { 236 this.clearFieldError(passwordConfirmationInput) 237 } 238 239 if (passwordInput.value.trim() && passwordConfirmationInput.value.trim() && 240 passwordInput.value !== passwordConfirmationInput.value) { 241 this.showFieldError(passwordConfirmationInput, "Les mots de passe ne correspondent pas") 242 hasError = true 243 } 244 } 245 246 if (!cguCheckbox.checked) { 247 this.showFieldError(cguCheckbox, "Vous devez accepter les CGU") 248 hasError = true 249 } else { 250 this.clearFieldError(cguCheckbox) 251 } 252 253 if (hasError) { 254 event.preventDefault() 255 return 256 } 257 258 // Désactiver le bouton et changer le texte 259 submitButton.disabled = true 260 if (submitText) { 261 submitText.textContent = "Inscription..." 262 } 263 } 264 265 showFieldError(input, message) { 266 input.classList.add("error") 267 268 // Retirer l'erreur précédente si elle existe 269 const existingError = input.parentElement.querySelector(".field-error") 270 if (existingError) { 271 existingError.remove() 272 } 273 274 // Ajouter le message d'erreur 275 const errorDiv = document.createElement("div") 276 errorDiv.className = "field-error" 277 errorDiv.textContent = message 278 279 // Pour les checkboxes, ajouter l'erreur après le label parent 280 if (input.type === "checkbox") { 281 input.closest(".cgu-checkbox").appendChild(errorDiv) 282 } else { 283 input.parentElement.appendChild(errorDiv) 284 } 285 } 286 287 clearFieldError(input) { 288 input.classList.remove("error") 289 const container = input.type === "checkbox" ? input.closest(".cgu-checkbox") : input.parentElement 290 const errorDiv = container.querySelector(".field-error") 291 if (errorDiv) { 292 errorDiv.remove() 293 } 294 } 295 296 validateForm() { 297 if (!this.hasSubmitButtonTarget) return 298 299 const authMethod = this.hasAuthMethodInputTarget ? this.authMethodInputTarget.value : 'email' 300 const prenom = this.hasPrenomInputTarget ? this.prenomInputTarget.value.trim() : ''
301 const nom = this.hasNomInputTarget ? this.nomInputTarget.value.trim() : '' 302 const email = this.hasEmailInputTarget ? this.emailInputTarget.value.trim() : '' 303 const phone = (authMethod === 'phone' && this.hasPhoneInputTarget) ? this.phoneInputTarget.value.trim() : '' 304 305 // Récupérer les mots de passe avec fallback 306 let password = '' 307 let passwordInputElement = null 308 309 if (this.hasPasswordInputTarget) { 310 passwordInputElement = this.passwordInputTarget 311 // Essayer de lire la valeur de plusieurs façons 312 const value1 = passwordInputElement.value 313 const value2 = passwordInputElement.getAttribute('value') 314 const value3 = passwordInputElement.defaultValue 315 // Utiliser la valeur brute sans trim pour éviter de perdre les valeurs qui ne sont que des espaces 316 // (même si ce n'est pas idéal, c'est pour le débogage - on trimmera après validation) 317 password = value1 || '' 318 } else { 319 // Fallback: chercher par data attribute, puis par name, puis par type 320 const byDataAttr = this.element.querySelector('[data-registrations-target="passwordInput"]') 321 const byName = this.element.querySelector('input[name="password"]') 322 const byTypePassword = this.element.querySelector('input[type="password"]:not([name="password_confirmation"])') 323 const byTypeText = this.element.querySelector('input[type="text"]:not([name="password_confirmation"])') 324 passwordInputElement = byDataAttr || byName || byTypePassword || byTypeText 325 if (passwordInputElement) { 326 password = passwordInputElement.value.trim() 327 } 328 } 329 330 let passwordConfirmation = '' 331 let passwordConfirmationElement = null 332 if (this.hasPasswordConfirmationInputTarget) { 333 passwordConfirmationElement = this.passwordConfirmationInputTarget 334 // Essayer de lire la valeur de plusieurs façons 335 const confValue1 = passwordConfirmationElement.value 336 const confValue2 = passwordConfirmationElement.getAttribute('value') 337 const confValue3 = passwordConfirmationElement.defaultValue 338 // Utiliser la valeur brute sans trim pour éviter de perdre les valeurs qui ne sont que des espaces 339 passwordConfirmation = confValue1 || '' 340 } else { 341 const byDataAttrConf = this.element.querySelector('[data-registrations-target="passwordConfirmationInput"]') 342 const byNameConf = this.element.querySelector('input[name="password_confirmation"]') 343 const allPasswordInputs = this.element.querySelectorAll('input[type="password"]') 344 const allTextInputs = this.element.querySelectorAll('input[type="text"]') 345 passwordConfirmationElement = byDataAttrConf || byNameConf || allPasswordInputs[1] || allTextInputs[1] 346 if (passwordConfirmationElement) { 347 passwordConfirmation = passwordConfirmationElement.value.trim() 348 } 349 } 350 351 // Récupérer l'état de la checkbox CGU avec plusieurs fallbacks 352 let cguAccepted = false 353 try { 354 if (this.hasCguCheckboxTarget) { 355 cguAccepted = this.cguCheckboxTarget.checked 356 } else { 357 // Fallback 1: chercher par data attribute 358 const cguCheckbox = this.element.querySelector('input[data-registrations-target="cguCheckbox"]') 359 if (cguCheckbox) { 360 cguAccepted = cguCheckbox.checked 361 } else { 362 // Fallback 2: chercher par classe 363 const cguCheckboxByClass = this.element.querySelector('.cgu-checkbox-input') 364 if (cguCheckboxByClass) { 365 cguAccepted = cguCheckboxByClass.checked 366 } else { 367 // Fallback 3: chercher par name 368 const cguCheckboxByName = this.element.querySelector('input[name="cgu_accepted"]') 369 if (cguCheckboxByName) { 370 cguAccepted = cguCheckboxByName.checked 371 } 372 } 373 } 374 } 375 } catch (e) { 376 console.error('Error getting CGU checkbox:', e) 377 } 378 379 // Vérifier que tous les champs sont remplis 380 const passwordTrimmed = password.trim() 381 const passwordConfirmationTrimmed = passwordConfirmation.trim() 382 const identifierValid = authMethod === 'phone' 383 ? Boolean(phone && phone.length >= 9) 384 : Boolean(email && email.includes('@')) 385 const allFieldsFilled = authMethod === 'phone' 386 ? Boolean(prenom && nom && phone) 387 : Boolean(prenom && nom && email && passwordTrimmed && passwordConfirmationTrimmed) 388 389 const passwordsMatch = authMethod === 'phone' || Boolean(passwordTrimmed && passwordConfirmationTrimmed && passwordTrimmed === passwordConfirmationTrimmed) 390 391 const isValid = Boolean(allFieldsFilled && passwordsMatch && identifierValid && cguAccepted) 392 393 this.submitButtonTarget.disabled = !isValid 394 } 395} 396;
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.