const restrictedKeyList = [ "ControlLeft", "ControlRight", "ShiftLeft", "AltLeft", "AltRight", "CapsLock", "ShiftRight", "VolumeUp", "VolumeDown", "AudioVolumeUp", "AudioVolumeDown", "Delete", "Tab", "MetaLeft", "MetaRight", "Unidentified", ]; const inputPassword = document.querySelector("#floating_password"); const inputPasswordConfirmation = document.querySelector( "#floating_repeat_password", ); const inputEmail = document.querySelector("#floating_email"); const inputDate = document.querySelector("#floating_date"); const inputPseudo = document.querySelector("#floating_pseudo"); const buttonRunCaptcha = document.querySelector("#button-run-captcha"); const buttonInscription = document.querySelector("#button-inscription"); const inscriptionContent = document.querySelector("#inscription-content"); const tabCaptcha = document.querySelector("#tab-captcha"); let previousTimeoutIdPassword, previousTimeoutIdEmail, previousTimeoutIdPseudo; let booleanPassword, booleanPasswordConfirmation, booleanEmail, booleanDate, booleanPseudo, booleanCaptcha; booleanPassword = booleanPasswordConfirmation = booleanEmail = booleanDate = booleanPseudo = booleanCaptcha = false; let isCaptchaRunning = false; let isVerificationRunning = false; let isVerificationCaptchaPuzRunning = false; let selectedSlotIndex = null; let currentShuffled; /** * Active le bouton "S'inscrire" une fois que toutes les conditions sont réunies * @param {boolean} disable Force le disabled si true */ function manageButtonInscription(disable = false) { const buttonInscription = document.querySelector("#button-inscription"); if (buttonInscription) { buttonInscription.disabled = disable || !( booleanPassword && booleanPassword && booleanPasswordConfirmation && booleanEmail && booleanDate && booleanPseudo && booleanCaptcha ); } } /** * Vérifie l'unicité du pseudo en db * @param {*} event */ function checkPseudo(event) { if (restrictedKeyList.includes(event.code)) { return; } const iconPseudo = document.querySelector("#floating-pseudo-icon"); if (iconPseudo) { iconPseudo.src = "/public/img/icon/spinner.svg"; iconPseudo.alt = "icon spinner"; iconPseudo.classList.add("animate-spin"); } else { const newIcon = document.createElement("img"); const inputPseudoDiv = document.querySelector("#floating-pseudo-div"); newIcon.id = "floating-pseudo-icon"; newIcon.src = "/public/img/icon/spinner.svg"; newIcon.alt = "icon spinner"; newIcon.classList.add("size-8", "animate-spin"); inputPseudoDiv.append(newIcon); } manageButtonInscription(true); if (previousTimeoutIdPseudo) { clearTimeout(previousTimeoutIdPseudo); } previousTimeoutIdPseudo = setTimeout(async () => { const currentPseudoInput = document.querySelector("#floating_pseudo"); const pseudo = currentPseudoInput.value; const pseudoErrorMessage = document.querySelector("#floating-pseudo-error-message"); const currentPseudoIcon = document.querySelector("#floating-pseudo-icon"); if (pseudo.length > 0) { try { const response = await fetch("/api/uniqueness", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pseudo: pseudo }) }); if (!response.ok) { throw new Error("Erreur serveur: veuillez réessayer."); } const data = await response.json(); if (!data.pseudo) { throw new Error("Le pseudo est déjà utilisé."); } currentPseudoInput.classList.remove("border-error-border"); pseudoErrorMessage.textContent = ""; currentPseudoIcon.src = "/public/img/icon/check.svg"; currentPseudoIcon.alt = "icon check"; currentPseudoIcon.classList.remove("animate-spin"); booleanPseudo = true; } catch (error) { currentPseudoInput.classList.add("border-error-border"); pseudoErrorMessage.textContent = error.message; currentPseudoIcon.src = "/public/img/icon/x.svg"; currentPseudoIcon.alt = "icon x"; currentPseudoIcon.classList.remove("animate-spin"); booleanPseudo = false; } } else { currentPseudoIcon.remove(); booleanPseudo = false; } manageButtonInscription(); }, 2000); } /** * Vérifie si la date entrée est valide * (pas de date dans le futur) */ function checkDate() { const currentInputDate = document.querySelector("#floating_date"); const dateErrorMessage = document.querySelector( "#floating-date-error-message", ); const now = new Date(Date.now()); const date = new Date(currentInputDate.value); manageButtonInscription(true); if (date >= now) { currentInputDate.classList.add("border-error-border"); dateErrorMessage.textContent = "Date invalide"; booleanDate = false; } else { currentInputDate.classList.remove("border-error-border"); dateErrorMessage.textContent = ""; booleanDate = true; } manageButtonInscription(); } /** * Vérifie si le mot de passe: * - contient au moins 12 caractères * - a 1 lettre * - a 1 chiffre * - a un caractère spécial * @param {string} password Le mot de passe * @returns true si valide, false sinon */ function checkPassword(password) { const regexChecks = [ /^.*[a-zA-Z]+.*$/, /^.*[0-9]+.*$/, /^.*[\.\#\?\!\@\$\%\&\*\_\+\=\:\;\^\-]+.*$/, ]; return ( password.length >= 12 && regexChecks.reduce((prev, regex) => { return prev && regex.test(password); }, true) ); } /** * Vérifie l'unicité du mail en db * @param {*} event */ function checkEmail(event) { if (restrictedKeyList.includes(event.code)) { return; } const iconEmail = document.querySelector("#floating-email-icon"); if (iconEmail) { iconEmail.src = "/public/img/icon/spinner.svg"; iconEmail.alt = "icon spinner"; iconEmail.classList.add("animate-spin"); } else { const newIcon = document.createElement("img"); const inputEmailDiv = document.querySelector("#floating-email-div"); newIcon.id = "floating-email-icon"; newIcon.src = "/public/img/icon/spinner.svg"; newIcon.alt = "icon spinner"; newIcon.classList.add("size-8", "animate-spin"); inputEmailDiv.append(newIcon); } manageButtonInscription(true); if (previousTimeoutIdEmail) { clearTimeout(previousTimeoutIdEmail); } previousTimeoutIdEmail = setTimeout(async () => { const currentEmailInput = document.querySelector("#floating_email"); const currentEmailIcon = document.querySelector("#floating-email-icon"); const emailErrorMessage = document.querySelector( "#floating-email-error-message", ); const email = currentEmailInput.value; if (email.length > 0) { try { if (!/^[a-zA-Z0-9.]+@[a-z]+\.[a-z]+$/g.test(email)) { throw new Error("L'email n'est pas correct."); } const response = await fetch("/api/uniqueness", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email }) }); if (!response.ok) { throw new Error("Erreur serveur, veuillez réessayer."); } const data = await response.json(); if (!data.email) { throw new Error("L'email est déjà utilisé."); } currentEmailInput.classList.remove("border-error-border"); emailErrorMessage.textContent = ""; currentEmailIcon.src = "/public/img/icon/check.svg"; currentEmailIcon.alt = "icon check"; currentEmailIcon.classList.remove("animate-spin"); booleanEmail = true; } catch (error) { currentEmailInput.classList.add("border-error-border"); emailErrorMessage.textContent = error.message; currentEmailIcon.src = "/public/img/icon/x.svg"; currentEmailIcon.alt = "icon x"; currentEmailIcon.classList.remove("animate-spin"); booleanEmail = false; } } else { currentEmailIcon.remove(); booleanEmail = false; } manageButtonInscription(); }, 2000); } /** * Vérifie si le mot de passe respecte les conditions, * et que la confirmation est identique * @param {*} event */ function checkConfirmation(event) { if (restrictedKeyList.includes(event.code)) { return; } const iconPassword = document.querySelector("#floating-password-icon"); const iconPasswordConfirmation = document.querySelector( "#floating-repeat-password-icon", ); if (iconPasswordConfirmation !== null) { iconPasswordConfirmation.src = "/public/img/icon/spinner.svg"; iconPasswordConfirmation.alt = "icon spinner"; iconPasswordConfirmation.classList.add("animate-spin"); } else { const newIcon = document.createElement("img"); const currentInputPasswordConfirmationDiv = document.querySelector( "#floating-repeat-password-div", ); newIcon.id = "floating-repeat-password-icon"; newIcon.src = "/public/img/icon/spinner.svg"; newIcon.alt = "icon spinner"; newIcon.classList.add("size-8", "animate-spin"); currentInputPasswordConfirmationDiv.append(newIcon); } if (iconPassword !== null) { iconPassword.src = "/public/img/icon/spinner.svg"; iconPassword.alt = "icon spinner"; iconPassword.classList.add("animate-spin"); } else { const newIcon = document.createElement("img"); const currentInputPasswordDiv = document.querySelector( "#floating-password-div", ); newIcon.id = "floating-password-icon"; newIcon.src = "/public/img/icon/spinner.svg"; newIcon.alt = "icon spinner"; newIcon.classList.add("size-8", "animate-spin"); currentInputPasswordDiv.append(newIcon); } manageButtonInscription(true); if (previousTimeoutIdPassword !== undefined) { clearTimeout(previousTimeoutIdPassword); } previousTimeoutIdPassword = setTimeout(() => { const currentInputPassword = document.querySelector("#floating_password"); const currentInputPasswordConfirmation = document.querySelector( "#floating_repeat_password", ); const currentIconPasswordConfirmation = document.querySelector( "#floating-repeat-password-icon", ); const currentIconPassword = document.querySelector( "#floating-password-icon", ); const password = currentInputPassword.value; const confirmation = currentInputPasswordConfirmation.value; const passwordErrorMessage = document.querySelector( "#floating-password-error-message", ); const passwordConfirmationErrorMessage = document.querySelector( "#floating-repeat-password-error-message", ); if (password.length > 0) { if (!checkPassword(password)) { const specialChars = document.createElement("strong"); inputPassword.classList.add("border-error-border"); passwordErrorMessage.textContent = "Doit contenir au moins 12 caractères dont 1 lettre, 1 chiffre et 1 caractère spécial parmi "; specialChars.textContent = ".#?!@$%&*_-+=:;^"; passwordErrorMessage.append(specialChars); currentIconPassword.src = "/public/img/icon/x.svg"; currentIconPassword.alt = "icon x"; currentIconPassword.classList.remove("animate-spin"); booleanPassword = false; } else { inputPassword.classList.remove("border-error-border"); passwordErrorMessage.textContent = ""; passwordErrorMessage.replaceChildren(); currentIconPassword.src = "/public/img/icon/check.svg"; currentIconPassword.alt = "icon check"; currentIconPassword.classList.remove("animate-spin"); booleanPassword = true; } } else { currentIconPassword.remove(); } if (confirmation.length > 0) { if (password !== confirmation) { inputPasswordConfirmation.classList.add("border-error-border"); passwordConfirmationErrorMessage.textContent = "Les mots de passe ne correspondent pas."; currentIconPasswordConfirmation.src = "/public/img/icon/x.svg"; currentIconPasswordConfirmation.alt = "icon x"; currentIconPasswordConfirmation.classList.remove("animate-spin"); booleanPasswordConfirmation = false; } else { inputPasswordConfirmation.classList.remove("border-error-border"); passwordConfirmationErrorMessage.textContent = ""; currentIconPasswordConfirmation.src = "/public/img/icon/check.svg"; currentIconPasswordConfirmation.alt = "icon check"; currentIconPasswordConfirmation.classList.remove("animate-spin"); booleanPasswordConfirmation = true; } } else { currentIconPasswordConfirmation.remove(); booleanPasswordConfirmation = false; } manageButtonInscription(); }, 2000); } /** * Récupère un captcha et lance l'onglet Captcha */ async function runCaptcha() { /** * Change la question affichée du captcha * @param {*} button Bouton relié à la question * @param {*} ref L'ID de la question * @returns La fonction exécutant la tâche */ function navigateToQuestion(button, ref) { return function() { const questions = document.querySelectorAll(".rcq-question"); const navButtons = document.querySelectorAll(".rcq-navbutton"); questions.forEach((element) => { if (element.id !== ref) { element.classList.add("hidden"); } else { element.classList.remove("hidden"); } }); navButtons.forEach((element) => { element.classList.remove("bg-secondary"); element.classList.remove("w-(--navbutton-width-focused)"); element.classList.add("bg-gray-200"); element.classList.add("w-(--navbutton-width-unfocused)"); }); button.classList.remove("bg-gray-200"); button.classList.remove("w-(--navbutton-width-unfocused)"); button.classList.add("bg-secondary"); button.classList.add("w-(--navbutton-width-focused)"); }; } if (isCaptchaRunning) { console.warn("Captcha already running."); return; } let captcha; try { const response = await fetch("/api/captcha/run"); captcha = await response.json(); } catch(error) { console.error("Erreur inscription captcha fetch: " + error); return; } const pageCaptcha = document.querySelector("#page-captcha"); const templateCaptcha = document.querySelector("#template-run-captcha-questions").content.cloneNode(true); const verificationButton = templateCaptcha.querySelector("#button-captcha-verification"); const questionContainer = templateCaptcha.querySelector("#captcha-questions"); const navigationContainer = templateCaptcha.querySelector("#captcha-navigation"); let isDisplayingQuestion = false; for (const [qid, qvalue] of Object.entries(captcha.questions) ) { const questionId = qid.substring(2); const templateNavButton = document.querySelector("#template-rcq-navbutton").content.cloneNode(true); const templateQuestion = document.querySelector("#template-rcq-question").content.cloneNode(true); const navButton = templateNavButton.querySelector("button"); const questionDiv = templateQuestion.querySelector("div"); const questionSpan = templateQuestion.querySelector("div span"); const reponseContainer = templateQuestion.querySelector("div div"); questionDiv.id = `q-${questionId}`; questionSpan.textContent = qvalue.contenu; navButton.addEventListener("click", navigateToQuestion(navButton, `q-${questionId}`)); if (!isDisplayingQuestion) { questionDiv.classList.remove("hidden"); } else { navButton.classList.remove("bg-secondary"); navButton.classList.remove("w-(--navbutton-width-focused)"); navButton.classList.add("bg-gray-200"); navButton.classList.add("w-(--navbutton-width-unfocused)"); } for (const [rid, rvalue] of Object.entries(qvalue.reponses)) { const reponseId = rid.substring(2); const templateReponse = document.querySelector("#template-rcq-reponse").content.cloneNode(true); const reponseLabel = templateReponse.querySelector("label"); const reponseSpan = templateReponse.querySelector("label span"); const reponseInput = templateReponse.querySelector("label input"); reponseLabel.setAttribute("for", `q-${questionId}_r-${reponseId}`); reponseSpan.textContent = rvalue.contenu; reponseInput.id = `q-${questionId}_r-${reponseId}`; reponseInput.name = `q-${questionId}`; reponseContainer.append(templateReponse); } questionContainer.append(templateQuestion); navigationContainer.append(templateNavButton); isDisplayingQuestion = true; } verificationButton.addEventListener("click", verifyCaptcha); pageCaptcha.append(templateCaptcha); pageCaptcha.classList.remove("sm:hidden"); tabCaptcha.classList.remove("hidden"); isCaptchaRunning = true; } async function runCaptchaPuz() { if (isCaptchaRunning) { console.warn("Captcha already running."); return; } try { const response = await fetch("/api/captcha/puzzle/run"); const data = await response.json(); if (!response.ok) { throw new Error("response not ok"); } const pageCaptcha = document.querySelector("div#page-captcha"); const templateRunCaptchaPuz = document.querySelector("template#tp-run-cpuz").content.cloneNode(true); const container = templateRunCaptchaPuz.querySelector("div.grid"); const buttonVerification = templateRunCaptchaPuz.querySelector("button#btn-cpuz-verification"); currentShuffled = data.state; function renderPuzzlePiece(index) { const piece = currentShuffled[index]; const rendered_pieces = document.querySelectorAll("div.cpuz-piece"); rendered_pieces.forEach((piece) => { if (piece.dataset.slotIndex === index) { piece.style.backgroundPosition = `${-(currentShuffled[index] % data.cols * data.piece_width)}px ${-(Math.floor(currentShuffled[index] / data.cols) * data.piece_height)}px`; } }); } container.style.gridTemplateColumns = `repeat(${data.cols}, ${data.piece_width}px)`; for (let i = 0; i < currentShuffled.length; i++) { const piece = currentShuffled[i]; const templateRunCaptchaPuzItem = document.querySelector("template#tp-run-cpuz-piece").content.cloneNode(true); const div = templateRunCaptchaPuzItem.querySelector("div"); function puzzlePieceOnClick() { if (selectedSlotIndex === null) { selectedSlotIndex = div.dataset.slotIndex; div.classList.add("selected"); } else { const temp = currentShuffled[selectedSlotIndex]; currentShuffled[selectedSlotIndex] = currentShuffled[div.dataset.slotIndex]; currentShuffled[div.dataset.slotIndex] = temp; renderPuzzlePiece(selectedSlotIndex); renderPuzzlePiece(div.dataset.slotIndex); selectedSlotIndex = null; } } div.style.width = `${data.piece_width}px`; div.style.height = `${data.piece_height}px`; div.style.backgroundImage = `url("${data.image.substring(9)}")`; div.style.backgroundPosition = `${-(piece % data.cols * data.piece_width)}px ${-(Math.floor(piece / data.cols) * data.piece_height)}px`; div.setAttribute("data-slot-index", i); div.addEventListener("click", puzzlePieceOnClick); container.append(templateRunCaptchaPuzItem); } isCaptchaRunning = true; pageCaptcha.classList.remove("sm:hidden"); tabCaptcha.classList.remove("hidden"); buttonVerification.addEventListener("click", verifyCaptchaPuz); pageCaptcha.append(templateRunCaptchaPuz); } catch (e) { throw e; // console.error("runCaptchaPuz: " + e); // return; } } /** * Récupère les réponses du captcha et les envoie à la vérification */ async function verifyCaptcha() { if (isVerificationRunning) { console.warn("verification already running"); return; } const icon = this.querySelector("img"); icon.src = "/public/img/icon/spinner.svg"; icon.alt = "icon spinner"; icon.classList.add("animate-spin"); // 3s sleep pour ne pas spam le captcha isVerificationRunning = true; setTimeout(async () => { const reponses = document.querySelectorAll(".captcha-reponse-input"); const body = { "submitted": [] }; for (const reponse of reponses) { if (reponse.checked) { body.submitted.push({"label": reponse.id}); } } const response = await fetch("/api/captcha/verify", { method: "POST", headers: { "Content-type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) { console.error("Verify captcha reponse not ok"); } const data = await response.json(); if (data.verified) { icon.src = "/public/img/icon/check.svg"; icon.alt = "icon check"; booleanCaptcha = true; this.disabled = true; buttonRunCaptcha.disabled = true; } else { icon.src = "/public/img/icon/x.svg"; icon.alt = "icon x"; setTimeout(() => { icon.src = "/public/img/icon/arrow_up_right.svg"; icon.alt = "icon arrow up right"; isVerificationRunning = false; booleanCaptcha = false; }, 1500); } manageButtonInscription(); icon.classList.remove("animate-spin"); }, 3000); } async function verifyCaptchaPuz() { if (isVerificationCaptchaPuzRunning) { console.warn("verification already running"); return; } const icon = this.querySelector("img"); icon.src = "/public/img/icon/spinner.svg"; icon.alt = "icon spinner"; icon.classList.add("animate-spin"); // 3s sleep pour ne pas spam le captcha isVerificationCaptchaPuzRunning = true; setTimeout(async () => { const body = { "submitted": currentShuffled }; const response = await fetch("/api/captcha/puzzle/verify", { method: "POST", headers: { "Content-type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) { console.error("Verify captcha reponse not ok"); } const data = await response.json(); if (data.verified) { icon.src = "/public/img/icon/check.svg"; icon.alt = "icon check"; booleanCaptcha = true; this.disabled = true; buttonRunCaptcha.disabled = true; } else { icon.src = "/public/img/icon/x.svg"; icon.alt = "icon x"; setTimeout(() => { icon.src = "/public/img/icon/arrow_up_right.svg"; icon.alt = "icon arrow up right"; isVerificationCaptchaPuzRunning = false; booleanCaptcha = false; }, 1500); } manageButtonInscription(); icon.classList.remove("animate-spin"); }, 3000); } tabCaptcha.classList.add("hidden"); inputPassword?.addEventListener("keydown", checkConfirmation); inputPasswordConfirmation?.addEventListener("keydown", checkConfirmation); inputEmail?.addEventListener("keydown", checkEmail); inputDate?.addEventListener("input", checkDate); inputPseudo?.addEventListener("keydown", checkPseudo); buttonRunCaptcha?.addEventListener("click", runCaptchaPuz); buttonRunCaptcha?.addEventListener("click", () => activate(activeIndex));