This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)">
|
||||
|
||||
+4
-1
@@ -49,10 +49,13 @@ export default class Api {
|
||||
}
|
||||
|
||||
const oResponse = await oRequest.json();
|
||||
oResponse.descKey = this.lang.getLangKey(oResponse.desc);
|
||||
oResponse.desc = this.lang.parse(oResponse.desc);
|
||||
|
||||
if(oResponse.result == this.errorCode) {
|
||||
throw oResponse.desc;
|
||||
const oError = new Error(oResponse.desc);
|
||||
oError.descKey = oResponse.descKey;
|
||||
throw oError;
|
||||
}
|
||||
|
||||
return oResponse;
|
||||
|
||||
@@ -32,6 +32,9 @@ import {
|
||||
faLayerGroup,
|
||||
faLink,
|
||||
faLocationPin,
|
||||
faRightFromBracket,
|
||||
faRightToBracket,
|
||||
faUser,
|
||||
faMagnifyingGlass,
|
||||
faMagnifyingGlassLocation,
|
||||
faMapLocationDot,
|
||||
@@ -47,7 +50,6 @@ import {
|
||||
faTemperatureThreeQuarters,
|
||||
faTriangleExclamation,
|
||||
faVideo,
|
||||
faWifi,
|
||||
faWind
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@@ -100,8 +102,10 @@ const ICONS = {
|
||||
download: faFileArrowDown,
|
||||
start: faCirclePlay,
|
||||
|
||||
/* Admin */
|
||||
newsletter: faWifi,
|
||||
/* Settings */
|
||||
account: faUser,
|
||||
login: faRightToBracket,
|
||||
logout: faRightFromBracket,
|
||||
project: faPersonHiking,
|
||||
unsubscribe: faCircleXmark,
|
||||
credits: faPaw,
|
||||
|
||||
+6
-4
@@ -23,9 +23,11 @@ export default class Lang {
|
||||
}
|
||||
|
||||
parse(message = '') {
|
||||
if(this.prefix && typeof message === 'string' && message.startsWith(this.prefix)) {
|
||||
return this.get(message.slice(this.prefix.length));
|
||||
}
|
||||
return message;
|
||||
let sLangKey = this.getLangKey(message);
|
||||
return (sLangKey != '')?this.get(sLangKey):message;
|
||||
}
|
||||
|
||||
getLangKey(message = '') {
|
||||
return (this.prefix && typeof message === 'string' && message.startsWith(this.prefix))?message.slice(this.prefix.length):'';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,10 @@ h2 {
|
||||
|
||||
.feedback {
|
||||
p {
|
||||
margin: 0 0 var.$block-spacing 0;
|
||||
margin: 0;
|
||||
& + p {
|
||||
margin-top: var.$block-spacing;
|
||||
}
|
||||
&.error {
|
||||
color: color.$error;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
color: color.$default-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ $panel-actual-width: min($panel-width, #{$panel-width-max});
|
||||
color: color.$default-inv;
|
||||
|
||||
&:hover, &:hover a, &:hover a:visited {
|
||||
background-color: color.$default-bg;
|
||||
background-color: color.$default-bg-light;
|
||||
color: color.$default;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
margin-top: var.$block-spacing - var.$elem-spacing; //Gap counts for 1 $elem-spacing
|
||||
font-size: 0.8em;
|
||||
color: color.$subtitle;
|
||||
text-align: center;
|
||||
|
||||
.find-me-spot {
|
||||
color: color.$spot;
|
||||
@@ -41,7 +42,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.settings-sections {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
@@ -97,16 +97,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-section + .sub-section {
|
||||
margin-top: var.$block-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.newsletter {
|
||||
.newsletter-form {
|
||||
&.account {
|
||||
.account-login {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
gap: var.$block-spacing;
|
||||
margin-bottom: var.$block-spacing;
|
||||
|
||||
input#email {
|
||||
input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user