PageSourceSearch

https://tambali.ai/assets/controllers/registrations_controller-8a7…32331a47467a59a5b9e07012df0e8ce6f630f165.js

js tambali.ai collected 2026-10-05 14:25:20 UTC 14,202 bytes, 396 lines download raw bytes

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.