From 3e1af0917ecfb1cee5410ac9233728c8f91472eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Poussard?= <tpoussard@neogeo.fr>
Date: Tue, 18 Mar 2025 17:17:51 +0100
Subject: [PATCH] =?UTF-8?q?feat(signup):=20envoie=20des=20requ=C3=AAtes=20?=
 =?UTF-8?q?d'inscription?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/MessageInfo.vue |  29 ++++-
 src/router/index.js            |   7 ++
 src/services/user-api.js       |  20 ++++
 src/store/index.js             |  17 ---
 src/views/Login.vue            | 189 ++++++++++++++++++++++++---------
 5 files changed, 188 insertions(+), 74 deletions(-)
 create mode 100644 src/services/user-api.js

diff --git a/src/components/MessageInfo.vue b/src/components/MessageInfo.vue
index ec61554a..57447916 100644
--- a/src/components/MessageInfo.vue
+++ b/src/components/MessageInfo.vue
@@ -12,10 +12,10 @@
         />
         <div class="header">
           <i
-            class="info circle icon"
+            :class="[headerIcon, 'circle icon']"
             aria-hidden="true"
           />
-          Informations
+          {{ message.header || 'Informations' }}
         </div>
         <ul class="list">
           {{
@@ -49,6 +49,17 @@ export default {
 
   computed: {
     ...mapState(['messages']),
+
+    headerIcon() {
+      switch (this.message.level) {
+      case 'positive':
+        return 'check';
+      case 'negative':
+        return 'times';
+      default:  
+        return 'info';
+      }
+    }
   },
 
   mounted() {
@@ -83,7 +94,12 @@ export default {
     transition: all 0.6s ease-out;
 }
 .list-container.show{
-  height: 6em;
+  height: 7.5em;
+}
+@media screen and (min-width: 726px) {
+  .list-container.show{
+    height: 6em;
+  }
 }
 .list-container.show:not(:first-child){
   margin-top: 10px;
@@ -103,9 +119,14 @@ export default {
 
 ul.list{
   overflow: auto;
-  height: 2.2em;
+  height: 3.5em;
   margin-bottom: .5em !important;
 }
+@media screen and (min-width: 726px) {
+  ul.list{
+    height: 2.2em;
+  }
+}
 
 .ui.message {
   overflow: hidden;
diff --git a/src/router/index.js b/src/router/index.js
index d0f2883b..0323e129 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -20,6 +20,7 @@ const routes = [
   {
     path: `${projectBase === 'projet' ? '': `/${projectBase}/:slug`}/connexion/`,
     name: 'login',
+    props: true,
     component: () => import('../views/Login.vue')
   },
   {
@@ -27,6 +28,12 @@ const routes = [
     name: 'signup',
     component: () => import('../views/Login.vue')
   },
+  {
+    path: `${projectBase === 'projet' ? '': `/${projectBase}/:slug`}/inscription/succes`,
+    name: 'sso-signup-success',
+    props: true,
+    component: () => import('../views/Login.vue')
+  },
   {
     path: `${projectBase === 'projet' ? '': `/${projectBase}/:slug`}/my_account/`,
     name: 'my_account',
diff --git a/src/services/user-api.js b/src/services/user-api.js
new file mode 100644
index 00000000..e06aedda
--- /dev/null
+++ b/src/services/user-api.js
@@ -0,0 +1,20 @@
+import axios from '@/axios-client.js';
+import store from '../store';
+
+
+const baseUrl = store.state.configuration.VUE_APP_DJANGO_API_BASE;
+
+const userAPI = {
+  async signup(data, url) {
+    try {
+      const response = await axios.post(url || `${baseUrl}v2/users/`, data);
+      return response;  // Retourne directement la réponse si succès
+    } catch (err) {
+      console.error("Erreur lors de l'inscription :", err.response || err);
+      return err.response || { status: 500, data: { detail: "Erreur inconnue" } };  // 👈 Retourne la réponse d'erreur si disponible
+    }
+  },
+};
+
+
+export default userAPI;
diff --git a/src/store/index.js b/src/store/index.js
index 773883a4..ff20b3c0 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -148,23 +148,6 @@ export default new Vuex.Store({
         });
     },
 
-    SIGNUP({ commit }, payload) {
-      if (payload.first_name && payload.last_name && payload.username && payload.email && payload.password) {
-        return axios
-          .post('https://fenigs.neogeo.fr/fr/login/signup/', payload)
-          .then((response) => {
-            if (response.status === 201 && response.data) {
-              console.log('response.data', response.data);
-            }
-          })
-          .catch((err) => {
-            console.error(err);
-            commit('SET_USER', false);
-            return 'error';
-          });
-      }
-    },
-
     LOGIN({ commit, dispatch }, payload) {
       if (payload.username && payload.password) {
         return axios
diff --git a/src/views/Login.vue b/src/views/Login.vue
index aa37555c..4becd170 100644
--- a/src/views/Login.vue
+++ b/src/views/Login.vue
@@ -78,7 +78,7 @@
         </form>
       </div>
       <div
-        v-if="$route.name === 'signup'"
+        v-else-if="$route.name === 'signup'"
         class="six wide column"
       >
         <h3 class="ui horizontal divider header">
@@ -172,7 +172,7 @@
                   name="password"
                   placeholder="Mot de passe"
                   required
-                  @blur="checkPwd"
+                  @blur="isValidPwd"
                 >
                 <button
                   class="ui icon button"
@@ -193,16 +193,18 @@
                   v-model="signupForm.comments"
                   type="text"
                   name="comments"
-                  :placeholder="commentsFieldLabel || `Commentaires (optionnel)`"
+                  :placeholder="commentsFieldLabel || `Commentaires`"
                   :required="commentsFieldRequired"
                 >
               </div>
             </div>
-            <div class="ui divider" />
-            <div class="six field">
+            <div
+              v-if="usersGroupsOptions.length > 0"
+              class="six field"
+            >
+              <div class="ui divider" />
               <Multiselect
-                v-if="usersGroupsOptions.length > 0"
-                v-model="signupForm.usersgroups"
+                v-model="usersGroupsSelections"
                 :options="usersGroupsOptions"
                 :multiple="true"
                 track-by="value"
@@ -213,14 +215,14 @@
                 :searchable="false"
                 :placeholder="'Sélectionez un ou plusieurs groupe de la liste ...'"
               />
+              <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>
             </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', {'disabled': !allFilled || error}]"
+              :class="['ui fluid large teal submit button']"
               type="submit"
             >
               Valider
@@ -228,13 +230,38 @@
           </div>
         </form>
       </div>
+      <div
+        v-else-if="$route.name === 'sso-signup-success'"
+        class="six wide column"
+      >
+        <h3 class="ui horizontal divider header">
+          INSCRIPTION RÉUSSIE
+        </h3>
+        <h4 class="ui center aligned icon header">
+          <div class="content">
+            <p
+              v-if="username"
+              class="sub header"
+            >
+              Le compte pour le nom d'utilisateur <strong>{{ username }}</strong> a été créé
+            </p>
+            <p>
+              Un e-mail de confirmation vient d'être envoyé à l'adresse indiquée.
+            </p>
+            <p class="sub header">
+              Merci de bien vouloir suivre les instructions données afin de finaliser la création de votre compte.
+            </p>
+          </div>
+        </h4>
+      </div>
     </div>
   </div>
 </template>
 
 <script>
-import { mapState, mapGetters } from 'vuex';
+import { mapState, mapGetters, mapMutations } from 'vuex';
 import Multiselect from 'vue-multiselect';
+import userAPI from '../services/user-api';
 
 export default {
   name: 'Login',
@@ -243,6 +270,13 @@ export default {
     Multiselect
   },
 
+  props: {
+    username: {
+      type: String,
+      default: null
+    }
+  },
+
   data() {
     return {
       logged: false,
@@ -257,7 +291,7 @@ export default {
         last_name: null,
         email: null,
         comments: null,
-        usersgroups: null,
+        usersgroups: [],
       },
       errors: {
         global: null,
@@ -265,7 +299,6 @@ export default {
         comments: null,
       },
       showPwd: false,
-      allFilled: false,
     };
   },
 
@@ -275,11 +308,21 @@ export default {
       appName: state => state.configuration.VUE_APP_APPLICATION_NAME,
       appAbstract: state => state.configuration.VUE_APP_APPLICATION_ABSTRACT,
       adminMail: state => state.configuration.VUE_APP_ADMIN_MAIL,
+      ssoSignupUrl: state => state.configuration.VUE_APP_SSO_SIGNUP_URL,
       commentsFieldLabel: state => state.configuration.VUE_APP_SIGNUP_COMMENTS_FIELD_LABEL,
       commentsFieldRequired: state => state.configuration.VUE_APP_SIGNUP_COMMENTS_FIELD_REQUIRED,
     }),
     ...mapGetters(['usersGroupsOptions']),
 
+    usersGroupsSelections: {
+      get() {
+        return this.usersGroupsOptions.filter((el) => this.signupForm.usersgroups?.includes(el.value));
+      },
+      set(newValue) {
+        this.signupForm.usersgroups = newValue.map(el => el.value);
+      }
+    },
+
     error() {
       return this.errors.global || this.errors.passwd || this.errors.comments;
     }
@@ -288,27 +331,26 @@ export default {
   watch: {
     'signupForm.first_name': function (newValue, oldValue) {
       if (newValue !== oldValue) {
-        this.signupForm.username = `${newValue.charAt(0)}${this.signupForm.last_name}`.toLowerCase();
+        this.signupForm.username = `${newValue.charAt(0)}${this.signupForm.last_name}`.toLowerCase().replace(/\s/g, "");
       }
     },
     'signupForm.last_name': function (newValue, oldValue) {
       if (newValue !== oldValue) {
-        this.signupForm.username = `${this.signupForm.first_name.charAt(0)}${newValue}`.toLowerCase();
+        this.signupForm.username = `${this.signupForm.first_name.charAt(0)}${newValue}`.toLowerCase().replace(/\s/g, "");
       }
     },
     'signupForm.password': function (newValue, oldValue) {
       if (newValue.length >= 8) {
         if (newValue !== oldValue) {
-          this.checkPwd();
+          this.isValidPwd();
         }
       } else {
         this.errors.passwd = null;
       }
     },
-    signupForm: {
-      deep: true,
-      handler() {
-        this.allFilled = this.checkRequiredFields();
+    username(newValue, oldValue) {
+      if (newValue !== oldValue) {
+        this.loginForm.username = newValue; 
       }
     }
   },
@@ -320,16 +362,17 @@ export default {
   },
 
   mounted() {
-    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.' }
-      );
-      setTimeout(() => this.$store.dispatch('REDIRECT_AFTER_LOGIN'), 3100);
+    if (this.$route.name === 'login') {
+      if (this.$store.state.user) {
+        this.DISPLAY_MESSAGE({ header: 'Vous êtes déjà connecté', comment: 'Vous allez être redirigé vers la page précédente.' });
+        setTimeout(() => this.$store.dispatch('REDIRECT_AFTER_LOGIN'), 3100);
+      }
     }
   },
 
   methods: {
+    ...mapMutations(['DISPLAY_MESSAGE']),
+
     login() {
       this.$store
         .dispatch('LOGIN', {
@@ -346,46 +389,83 @@ export default {
         .catch();
     },
 
-    signup() {
-      this.errors.global = null;
-      this.$store
-        .dispatch('SIGNUP', {
-          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.errors.global = null;
-          } else if (status === 'error') {
-            this.errors.global = status;
+    async signup() {
+      if (this.hasUnvalidFields()) return;
+      
+      // Étape 1 : Création de l'utilisateur auprès du service d'authentification SSO si nécessaire
+      if (this.ssoSignupUrl) {
+        const ssoResponse = await userAPI.signup(this.signupForm, this.ssoSignupUrl);
+        
+        console.log(ssoResponse);
+        if (ssoResponse.status !== 201) {
+          if (ssoResponse.status === 400) {
+            this.errors.global = `Un compte associé à ce courriel existe déjà`;
+          } else {
+            this.errors.global = `Erreur lors de l'inscription: ${ssoResponse.data?.detail || "Problème inconnu"}`;
           }
-        })
-        .catch();
+          return;  // Stoppe la fonction si l'inscription SSO échoue
+        } else {
+          this.signupForm.username = ssoResponse.data.username;
+          this.signupForm.first_name = ssoResponse.data.first_name;
+          this.signupForm.last_name = ssoResponse.data.last_name;
+        }
+      }
+
+      // Étape 2 : Création de l'utilisateur dans Geocontrib
+      const response = await userAPI.signup(this.signupForm);
+
+      if (response.status !== 201) {
+        const errorMessage = response.data
+          ? Object.values(response.data)?.[0]?.[0] || "Problème inconnu"
+          : "Problème inconnu";
+        this.errors.global = `Erreur lors de l'inscription: ${errorMessage}`;
+        return;
+      }
+
+      this.DISPLAY_MESSAGE({ header: 'Inscription réussie !', comment: `Bienvenue sur la plateforme ${this.signupForm.username}.`, level: 'positive' });
+      if (this.ssoSignupUrl) {
+        setTimeout(() => {
+            this.$router.push({ name: 'sso-signup-success', params: {username: this.signupForm.username}});
+          }, 3100);
+      } else {
+        setTimeout(() => {
+          this.$router.push({ name: 'login', params: {username: this.signupForm.username}});
+        }, 3100);
+      }
     },
 
-    checkPwd() {
+
+    isValidPwd() {
       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;
-        }
+    hasUnvalidFields() {
+      const { last_name, email, first_name, comments } = this.signupForm;
+
+      if (this.commentsFieldRequired && !comments) {
+        this.errors.comments = `Le champ ${ this.commentsFieldLabel || 'Commentaires'} est requis`;
         return true;
+      } else {
+        this.errors.comments = null;
       }
+
+      if (email && last_name && first_name) {
+        this.errors.global = null;
+      } else {
+        this.errors.global = 'Certains champs requis ne sont pas renseignés'
+        return true;
+      }
+
+      return !this.isValidPwd();   
     }
   },
 };
@@ -415,6 +495,9 @@ export default {
     background-repeat: no-repeat;
   }
 }
+p {
+  margin: 1em 0 !important; 
+}
 </style>
 
 <style>
-- 
GitLab