Split authentication & newsletter subscription
Deploy Spot / deploy (push) Successful in 32s

This commit is contained in:
2026-08-10 16:41:20 +02:00
parent 3e3c3ee821
commit 462502cf9f
19 changed files with 628 additions and 481 deletions
+5 -5
View File
@@ -115,7 +115,7 @@ export default {
</script>
<template>
<div id="admin">
<a name="back" class="button" :href="getPrevAnchor()"><SpotIcon :icon="'back'" :text="l('action.back')" /></a>
<a name="back" class="button" :href="getPrevAnchor()"><SpotIcon :icon="'back'" :text="l('action.back')" /></a>
<h1>{{ l('project.plural') }}</h1>
<div>
<table>
@@ -195,27 +195,27 @@ export default {
</tbody>
</table>
</div>
<h1>{{ l('user.active') }}</h1>
<h1>{{ l('user.subscribed') }}</h1>
<div>
<table>
<thead>
<tr>
<th>{{ l('user.id') }}</th>
<th>{{ l('user.name') }}</th>
<th>{{ l('user.email') }}</th>
<th>{{ l('user.language') }}</th>
<th>{{ l('time.zone') }}</th>
<th>{{ l('user.clearance') }}</th>
<th>{{ l('action.delete') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="user in elems.user">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td class="left">{{ user.name }}</td>
<td class="left">{{ user.email }}</td>
<td>{{ user.language }}</td>
<td>{{ user.timezone }}</td>
<td><AdminInput :type="'number'" :name="'clearance'" :elem="user" /></td>
<td><SpotButton :icon="'close'" iconSize="lg" @click="deleteElem(user)" /></td>
</tr>
</tbody>
</table>
+113
View File
@@ -0,0 +1,113 @@
<script>
import SpotButton from '@components/spotButton';
import SpotIcon from '@components/spotIcon';
export default {
components: {
SpotButton,
SpotIcon
},
data() {
return {
feedbacks: [],
loginLoading: false,
newsletterLoading: false,
passwordMode: false,
settingPassword: false,
password: '',
passwordConfirmation: ''
};
},
computed: {
buttonClasses() {
return [
'manage',
this.action,
this.loginLoading?'loading':''
].filter(n => n).join(' ');
},
loggedIn() {
return this.user.id_user > 0;
},
action() {
return this.loggedIn?'logout':'login';
},
status() {
return this.loggedIn?(this.user.subscribed?'subscribed':(this.user.name?'logged_in_named':'logged_in')):'logged_out';
}
},
inject: ['api', 'lang', 'user'],
methods: {
manageSubscription() {
if(this.newsletterLoading) return;
this.newsletterLoading = true;
this.api.request(this.user.subscribed?'subscribe':'unsubscribe', {}, 'POST')
.then((asResponse) => {
this.user.setInfo(asResponse.data);
this.feedbacks.push({type:asResponse.result, msg:asResponse.desc});
})
.catch((sDesc) => {
this.user.subscribed = !this.user.subscribed;
this.feedbacks.push({type:'error', msg:sDesc?.message || sDesc});
})
.finally(() => {this.newsletterLoading = false;});
},
manageLogin() {
if(this.loginLoading) return;
var regexEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(!regexEmail.test(this.user.email)) this.feedbacks.push({type:'error', 'msg':this.lang.get('account.invalid_email')});
else if(this.settingPassword && this.password !== this.passwordConfirmation) this.feedbacks.push({type:'error', 'msg':this.lang.get('account.password_mismatch')});
else {
const sAction = this.action;
this.loginLoading = true;
this.api.request(sAction, {'email': this.user.email, 'password': this.password, 'name': this.user.name}, 'POST')
.then((asResponse) => {
this.feedbacks.push({type: asResponse.result, msg: asResponse.desc});
this.user.setInfo(asResponse.data);
this.passwordMode = false;
this.settingPassword = false;
this.password = '';
this.passwordConfirmation = '';
})
.catch((oError) => {
switch(oError.descKey) {
case 'account.set_password':
this.passwordMode = true;
this.settingPassword = true;
this.feedbacks.push({type:'warning', msg:oError.message});
break;
case 'account.password_required':
this.passwordMode = true;
this.feedbacks.push({type:'success', msg:oError.message});
break;
default:
this.feedbacks.push({type:'error', msg:oError.message});
}
})
.finally(() => {this.loginLoading = false;});
}
}
}
}
</script>
<template>
<div class="sub-section status">{{ lang.get('account.status.'+status, this.user.name) }}</div>
<div class="sub-section account-login">
<input type="email" name="email" id="email" :placeholder="lang.get('account.email_placeholder')" v-model="user.email" :disabled="loginLoading || loggedIn || passwordMode" @keyup.enter="manageLogin" />
<input v-if="passwordMode" type="password" name="password" id="password" :placeholder="lang.get('account.password')" v-model="password" :disabled="loginLoading" :autocomplete="settingPassword?'new-password':'current-password'" @keyup.enter="manageLogin" />
<input v-if="settingPassword" type="password" name="password_confirmation" id="password-confirmation" :placeholder="lang.get('account.confirm_password')" v-model="passwordConfirmation" :disabled="loginLoading" autocomplete="new-password" @keyup.enter="manageLogin" />
<SpotButton :classes="buttonClasses" :title="lang.get('account.'+action)" :icon="action" @click="manageLogin" />
</div>
<div class="sub-section newsletter radio" v-if="loggedIn">
<input type="checkbox" id="account-newsletter" v-model="user.subscribed" :true-value="1" :false-value="0" :disabled="newsletterLoading" @change="manageSubscription" />
<label for="account-newsletter">{{ lang.get('account.newsletter') }}</label>
</div>
<div v-if="feedbacks.length" class="sub-section feedback">
<p v-for="feedback in feedbacks" :key="feedback.type + '-' + feedback.msg" :class="feedback.type">
<SpotIcon :icon="feedback.type" :text="feedback.msg" />
</p>
</div>
</template>
-66
View File
@@ -1,66 +0,0 @@
<script>
import SpotButton from '@components/spotButton';
import SpotIcon from '@components/spotIcon';
export default {
components: {
SpotButton,
SpotIcon
},
data() {
return {
feedbacks: [],
loading: false
};
},
computed: {
buttonClasses() {
return [
'manage',
this.action,
this.loading?'loading':''
].filter(n => n).join(' ');
},
subscribed() {
return this.user.id_user > 0;
},
action() {
return this.subscribed?'unsubscribe':'subscribe';
}
},
inject: ['api', 'lang', 'user'],
methods: {
manage() {
if(this.loading) return;
var regexEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(!regexEmail.test(this.user.email)) this.feedbacks.push({type:'error', 'msg':this.lang.get('newsletter.invalid_email')});
else {
const sAction = this.action;
this.loading = true;
this.api.request(sAction, {'email': this.user.email, 'name': this.user.name}, 'POST')
.then((asResponse) => {
this.feedbacks.push({type: asResponse.result, msg: asResponse.desc});
this.user.setInfo(asResponse.data);
})
.catch((sDesc) => {this.feedbacks.push({type:'error', msg:sDesc?.message || sDesc});})
.finally(() => {this.loading = false;});
}
}
}
}
</script>
<template>
<div class="newsletter-form">
<input type="email" name="email" id="email" :placeholder="lang.get('newsletter.email_placeholder')" v-model="user.email" :disabled="loading || subscribed" />
<SpotButton :classes="buttonClasses" :title="lang.get('newsletter.'+action)" :icon="action" @click="manage" />
</div>
<div class="feedback">
<p v-for="feedback in feedbacks" :key="feedback.type + '-' + feedback.msg" :class="feedback.type">
<SpotIcon :icon="feedback.type" :text="feedback.msg" />
</p>
</div>
{{ lang.get('newsletter.'+(subscribed?'subscribed':'unsubscribed')+'_desc') }}
</template>
+5 -5
View File
@@ -2,14 +2,14 @@
import Simplebar from 'simplebar-vue';
import SpotIcon from '@components/spotIcon';
import ProjectNewsletter from '@components/projectNewsletter';
import ProjectAccount from '@components/projectAccount';
import logoIconUrl from '@images/icons/favicon.svg';
import logoTitleUrl from '@images/logo_title.svg';
export default {
components: {
SpotIcon,
ProjectNewsletter,
ProjectAccount,
Simplebar
},
props: {
@@ -125,10 +125,10 @@ export default {
</div>
</div>
</div>
<div class="settings-section settings-box newsletter">
<h1><SpotIcon :icon="'newsletter'" width="fixed" :text="lang.get('newsletter.title')" /></h1>
<div class="settings-section settings-box account">
<h1><SpotIcon :icon="'account'" width="fixed" :text="lang.get('account.title')" /></h1>
<div class="settings-section-body">
<ProjectNewsletter />
<ProjectAccount />
</div>
</div>
<div class="settings-section settings-box admin" v-if="user.hasClearance(consts.clearances.admin)">