From 8efb978c8449d098efdc5b5df70d835b952f5218 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Poussard?= <tpoussard@neogeo.fr>
Date: Mon, 17 Mar 2025 16:03:35 +0100
Subject: [PATCH] =?UTF-8?q?feat(signup):=20validation=20des=20champs=20et?=
 =?UTF-8?q?=20am=C3=A9lioration=20UI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../semantic-ui-2.4.2/semantic.min.css        |   8 +-
 src/store/index.js                            |  16 +-
 src/views/Login.vue                           | 239 ++++++++++++------
 3 files changed, 162 insertions(+), 101 deletions(-)

diff --git a/src/assets/resources/semantic-ui-2.4.2/semantic.min.css b/src/assets/resources/semantic-ui-2.4.2/semantic.min.css
index 9b65ec01..a9922b28 100644
--- a/src/assets/resources/semantic-ui-2.4.2/semantic.min.css
+++ b/src/assets/resources/semantic-ui-2.4.2/semantic.min.css
@@ -20033,10 +20033,10 @@ ol.ui.list li[value]:before {
     padding: 1em 1.5em;
     line-height: 1.4285em;
     color: #252525;
-    -webkit-transition: opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease;
-    transition: opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease;
-    transition: opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease;
-    transition: opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease, -webkit-box-shadow .5s ease;
+    -webkit-transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease;
+    transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease;
+    transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease;
+    transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease, -webkit-box-shadow .5s ease;
     border-radius: .07142857rem;
     -webkit-box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent;
     box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent
diff --git a/src/store/index.js b/src/store/index.js
index 0f35dd98..773883a4 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -149,26 +149,16 @@ export default new Vuex.Store({
     },
 
     SIGNUP({ commit }, payload) {
-      if (payload.first_name && payload.last_name && payload.username &&
-        payload.email && payload.password && payload.siret) {
-        console.log('payload', payload);
+      if (payload.first_name && payload.last_name && payload.username && payload.email && payload.password) {
         return axios
-          .post('https://fenigs.neogeo.fr/fr/login/signup/', {
-            first_name: payload.first_name,
-            last_name: payload.last_name,
-            username: payload.username,
-            email: payload.email,
-            password: payload.password,
-            comments: payload.siret,
-          })
+          .post('https://fenigs.neogeo.fr/fr/login/signup/', payload)
           .then((response) => {
-            console.log('response', response);
             if (response.status === 201 && response.data) {
               console.log('response.data', response.data);
             }
           })
           .catch((err) => {
-            console.error(err)
+            console.error(err);
             commit('SET_USER', false);
             return 'error';
           });
diff --git a/src/views/Login.vue b/src/views/Login.vue
index bc5d2aa9..aa37555c 100644
--- a/src/views/Login.vue
+++ b/src/views/Login.vue
@@ -26,10 +26,7 @@
           CONNEXION
         </h3>
 
-        <div
-          v-if="form.errors"
-          class="ui warning message"
-        >
+        <div :class="['ui warning message', {'closed': !errors.global}]">
           <div class="header">
             Les informations d'identification sont incorrectes.
           </div>
@@ -43,28 +40,28 @@
           @submit.prevent="login"
         >
           <div class="ui secondary segment">
-            <div class="six field required">
+            <div class="six field">
               <div class="ui left icon input">
                 <i
                   class="user icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.username"
+                  v-model="loginForm.username"
                   type="text"
                   name="username"
                   placeholder="Utilisateur"
                 >
               </div>
             </div>
-            <div class="six field required">
+            <div class="six field">
               <div class="ui left icon input">
                 <i
                   class="lock icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.password"
+                  v-model="loginForm.password"
                   type="password"
                   name="password"
                   placeholder="Mot de passe"
@@ -88,14 +85,8 @@
           INSCRIPTION
         </h3>
 
-        <div
-          v-if="form.errors"
-          class="ui warning message"
-        >
-          <div class="header">
-            Un compte associé à cette adresse courriel existe déjà.
-          </div>
-          Veuillez utiliser une adresse email différente ou vous connecter avec le compte associé à cette adresse courriel.
+        <div :class="['ui warning message', {'closed': !error}]">
+          {{ error }}
         </div>
 
         <form
@@ -105,60 +96,62 @@
           @submit.prevent="signup"
         >
           <div class="ui secondary segment">
-
-            <div class="six field required">
+            <div class="six field">
               <div class="ui left icon input">
                 <i
                   class="user outline icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.first_name"
+                  v-model="signupForm.first_name"
                   type="text"
                   name="first_name"
                   placeholder="Prénom"
+                  required
                 >
               </div>
             </div>
 
-            <div class="six field required">
+            <div class="six field">
               <div class="ui left icon input">
                 <i
                   class="id card icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.last_name"
+                  v-model="signupForm.last_name"
                   type="text"
                   name="last_name"
                   placeholder="Nom"
+                  required
                 >
               </div>
             </div>
 
-            <div class="six field required">
+            <div class="six field">
               <div class="ui left icon input">
                 <i
                   class="envelope icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.email"
+                  v-model="signupForm.email"
                   type="email"
                   name="email"
                   placeholder="Adresse courriel"
+                  required
                 >
               </div>
             </div>
 
-            <div class="six field required">
+            <div class="six field">
               <div class="ui left icon input">
                 <i
                   class="user icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.username"
+                  v-model="signupForm.username"
                   type="text"
                   name="username"
                   placeholder="Utilisateur"
@@ -167,37 +160,50 @@
               </div>
             </div>
 
-            <div class="six field required">
-              <div class="ui left icon input">
+            <div :class="['six field', {'error': errors.passwd}]">
+              <div class="ui action left icon input">
                 <i
                   class="lock icon"
                   aria-hidden="true"
                 />
                 <input
-                  v-model="form.password"
-                  type="password"
+                  v-model="signupForm.password"
+                  :type="showPwd ? 'text' : 'password'"
                   name="password"
                   placeholder="Mot de passe"
+                  required
+                  @blur="checkPwd"
+                >
+                <button
+                  class="ui icon button"
+                  @click="showPwd = !showPwd"
                 >
+                  <i :class="[showPwd ? 'eye slash' : 'eye', 'icon']" />
+                </button>
               </div>
             </div>
 
-            <div class="six field required">
-              <div class="ui labeled left icon input">
-                <label for="amount" class="ui label">SIRET</label>
+            <div :class="['six field', {'error': errors.comments}]">
+              <div class="ui left icon input">
+                <i
+                  class="pencil icon"
+                  aria-hidden="true"
+                />
                 <input
-                  v-model="form.siret"
-                  type="number"
-                  name="siret"
+                  v-model="signupForm.comments"
+                  type="text"
+                  name="comments"
+                  :placeholder="commentsFieldLabel || `Commentaires (optionnel)`"
+                  :required="commentsFieldRequired"
                 >
               </div>
             </div>
-
-            <div class="six field required">
+            <div class="ui divider" />
+            <div class="six field">
               <Multiselect
-                v-if="usersGroupsOptions"
-                v-model="form.usersgroups"
-                :options="usersGroupsOptions || []"
+                v-if="usersGroupsOptions.length > 0"
+                v-model="signupForm.usersgroups"
+                :options="usersGroupsOptions"
                 :multiple="true"
                 track-by="value"
                 label="name"
@@ -206,21 +212,15 @@
                 deselect-label=""
                 :searchable="false"
                 :placeholder="'Sélectionez un ou plusieurs groupe de la liste ...'"
-                />
-              </div>
-              <!-- 
-                :options-limit="10"
-                :reset-after="false"
-                :loading="loadingPrerecordedListValues"
-                :show-no-results="true"
-                :clear-on-select="false"
-                :preserve-search="false"
-                @search-change="search"
-                @select="selectPrerecordedValue"
-              -->
+              />
+            </div>
+            <p v-if="adminMail">
+              Si le groupe d'utilisateurs recherché n'apparaît pas, vous pouvez demander à un
+              <a :href="'mailto:'+adminMail">administrateur</a> de le créer
+            </p>
 
             <button
-              class="ui fluid large teal submit button"
+              :class="['ui fluid large teal submit button', {'disabled': !allFilled || error}]"
               type="submit"
             >
               Valider
@@ -246,17 +246,26 @@ export default {
   data() {
     return {
       logged: false,
-      form: {
-        errors: null,
+      loginForm: {
+        username: null,
+        password: null,
+      },
+      signupForm: {
+        username: null,
+        password: null,
         first_name: null,
         last_name: null,
-        username: null,
         email: null,
-        password: null,
-        password2: null,
-        siret: null,
+        comments: null,
         usersgroups: null,
       },
+      errors: {
+        global: null,
+        passwd: null,
+        comments: null,
+      },
+      showPwd: false,
+      allFilled: false,
     };
   },
 
@@ -265,19 +274,41 @@ export default {
       appLogo: state => state.configuration.VUE_APP_LOGO_PATH,
       appName: state => state.configuration.VUE_APP_APPLICATION_NAME,
       appAbstract: state => state.configuration.VUE_APP_APPLICATION_ABSTRACT,
+      adminMail: state => state.configuration.VUE_APP_ADMIN_MAIL,
+      commentsFieldLabel: state => state.configuration.VUE_APP_SIGNUP_COMMENTS_FIELD_LABEL,
+      commentsFieldRequired: state => state.configuration.VUE_APP_SIGNUP_COMMENTS_FIELD_REQUIRED,
     }),
     ...mapGetters(['usersGroupsOptions']),
+
+    error() {
+      return this.errors.global || this.errors.passwd || this.errors.comments;
+    }
   },
 
   watch: {
-    'form.first_name': function(newValue, oldValue) {
-      if (newValue && newValue !== oldValue && this.form.last_name) {
-        this.form.username = `${newValue.charAt(0)}${this.form.last_name}`.toLowerCase();
+    'signupForm.first_name': function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        this.signupForm.username = `${newValue.charAt(0)}${this.signupForm.last_name}`.toLowerCase();
+      }
+    },
+    'signupForm.last_name': function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        this.signupForm.username = `${this.signupForm.first_name.charAt(0)}${newValue}`.toLowerCase();
+      }
+    },
+    'signupForm.password': function (newValue, oldValue) {
+      if (newValue.length >= 8) {
+        if (newValue !== oldValue) {
+          this.checkPwd();
+        }
+      } else {
+        this.errors.passwd = null;
       }
     },
-    'form.last_name': function(newValue, oldValue) {
-      if (newValue && newValue !== oldValue && this.form.first_name) {
-        this.form.username = `${this.form.first_name.charAt(0)}${newValue}`.toLowerCase();
+    signupForm: {
+      deep: true,
+      handler() {
+        this.allFilled = this.checkRequiredFields();
       }
     }
   },
@@ -289,7 +320,7 @@ export default {
   },
 
   mounted() {
-    if (this.$store.state.user) {
+    if (this.$route.name === 'login' && this.$store.state.user) {
       this.$store.commit(
         'DISPLAY_MESSAGE',
         { comment: 'Vous êtes déjà connecté, vous allez être redirigé vers la page précédente.' }
@@ -297,46 +328,63 @@ export default {
       setTimeout(() => this.$store.dispatch('REDIRECT_AFTER_LOGIN'), 3100);
     }
   },
-  
+
   methods: {
     login() {
       this.$store
         .dispatch('LOGIN', {
-          username: this.form.username,
-          password: this.form.password,
+          username: this.loginForm.username,
+          password: this.loginForm.password,
         })
         .then((status) => {
           if (status === 200) {
-            this.form.errors = null;
+            this.errors.global = null;
           } else if (status === 'error') {
-            this.form.errors = status;
+            this.errors.global = status;
           }
         })
         .catch();
     },
+
     signup() {
-      this.form.errors = null;
+      this.errors.global = null;
       this.$store
         .dispatch('SIGNUP', {
-          first_name: this.form.first_name,
-          last_name: this.form.last_name,
-          username: this.form.username,
-          email: this.form.email,
-          password: this.form.password,
-          siret: this.form.siret,
+          first_name: this.signupForm.first_name,
+          last_name: this.signupForm.last_name,
+          username: this.signupForm.username,
+          email: this.signupForm.email,
+          password: this.signupForm.password,
+          comments: this.signupForm.comments,
         })
         .then((status) => {
           if (status === 200) {
-            this.form.errors = null;
+            this.errors.global = null;
           } else if (status === 'error') {
-            this.form.errors = status;
+            this.errors.global = status;
           }
         })
         .catch();
     },
-    checkPwdCreation() {
-      if (this.form.password !== this.form.password2) {
-        this.form.errors = 'Les mots de passe doivent être identique'
+
+    checkPwd() {
+      const regPwd = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d/$&+,:;=?#|'<>.^*()%!-]{8,}$/;
+      if (!regPwd.test(this.signupForm.password)) {
+        this.errors.passwd = `Le mot de passe doit comporter au moins 8 caractères, dont 1 majuscule, 1 minuscule et 1 chiffre.
+        Vous pouvez utiliser les caractères spéciaux suivants : /$ & + , : ; = ? # | ' < > . ^ * ( ) % ! -.`;
+        return false;
+      }
+      this.errors.passwd = null;
+      return true;
+    },
+
+    checkRequiredFields() {
+      if (this.signupForm.first_name && this.signupForm.last_name && this.signupForm.email
+        && this.signupForm.password && this.signupForm.password.length > 8) {
+        if (this.commentsFieldRequired && !this.signupForm.comments) {
+          return false;
+        }
+        return true;
       }
     }
   },
@@ -348,6 +396,24 @@ export default {
   max-width: 500px;
   min-width: 200px;
   margin: 3em auto;
+
+  .ui.message {
+    min-height: 0px;
+
+    &.closed {
+      overflow: hidden;
+      opacity: 0;
+      padding: 0;
+      max-height: 0px;
+    }
+  }
+
+  input[required] {
+    background-image: linear-gradient(45deg, transparent, transparent 50%, rgb(209, 0, 0) 50%, rgb(209, 0, 0) 100%);
+    background-position: top right;
+    background-size: .5em .5em;
+    background-repeat: no-repeat;
+  }
 }
 </style>
 
@@ -364,18 +430,23 @@ export default {
 }
 
 /* keep font-weight from overide of semantic classes */
-.multiselect__placeholder, .multiselect__content, .multiselect__tags  {
+.multiselect__placeholder,
+.multiselect__content,
+.multiselect__tags {
   font-weight: initial !important;
 }
+
 /* keep placeholder eigth */
 .multiselect .multiselect__placeholder {
   margin-bottom: 9px !important;
   padding-top: 1px;
 }
+
 /* keep placeholder height when opening dropdown without selection */
 input.multiselect__input {
   padding: 3px 0 0 0 !important;
 }
+
 /* keep placeholder height when opening dropdown with already a value selected */
 .multiselect__tags .multiselect__single {
   padding: 1px 0 0 0 !important;
-- 
GitLab