diff --git a/src/components/MessageInfo.vue b/src/components/MessageInfo.vue index ec61554a06157953f61df6b37666379457b1498d..5744791616bce1359473eafa73c3956e354cf463 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 d0f2883bfd63d52f0853f36603e92b060aab4241..0323e1290168f84878132500a54413755e9ca45e 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 0000000000000000000000000000000000000000..e06aedda841dc53021be6b422eb368c31ed39ee7 --- /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 773883a447ee36b3b1b0fbe874825ca597a9c79c..ff20b3c0d7a59b47b97f40634b52b212d0caf29d 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 aa37555c71a3963a22799d852fc2c243a7fc36b8..4becd1701559cd3c9213b43f0d8bb43e775ddda1 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>