Implemented personal settings
This commit is contained in:
100
frontend/src/components/SettingsWidget.vue
Normal file
100
frontend/src/components/SettingsWidget.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="settings-widget">
|
||||
<h2>{{ $t("settings.personal.title") }}</h2>
|
||||
<template v-for="setting in settings">
|
||||
<InputStringWidget v-if="setting.datatype == 'string'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value :list="languagesList()"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
<DateInputWidget v-else-if="setting.datatype == 'date'" :labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
<SelectDropdownWidget v-else-if="setting.datatype == 'singleselect'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
:list="getSettingOptions(setting.name, setting.options)" @input="handleInput(setting.id, $event)" />
|
||||
<div v-else>{{ setting }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import InputStringWidget from '@/components/form/InputStringWidget.vue';
|
||||
import DateInputWidget from '@/components/form/DateInputWidget.vue';
|
||||
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget';
|
||||
|
||||
export default {
|
||||
name: "SettingsWidget",
|
||||
components: {
|
||||
InputStringWidget,
|
||||
DateInputWidget,
|
||||
SelectDropdownWidget,
|
||||
},
|
||||
props: {
|
||||
settingsType: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchSettings();
|
||||
},
|
||||
methods: {
|
||||
async fetchSettings() {
|
||||
if (this.user && this.user.id) {
|
||||
try {
|
||||
const userid = this.user.id;
|
||||
const response = await apiClient.post('/api/settings/filter', {
|
||||
userid: userid,
|
||||
type: this.settingsType
|
||||
});
|
||||
this.settings = response.data;
|
||||
} catch (err) {
|
||||
this.settings = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
getSettingOptions(fieldName, options) {
|
||||
return options.map((option) => {
|
||||
return {
|
||||
value: option.id,
|
||||
captionTr: `settings.personal.${fieldName}.${option.value}`
|
||||
}
|
||||
});
|
||||
},
|
||||
async handleInput(settingId, value) {
|
||||
if (['object', 'array'].includes(typeof value)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const userid = this.user.id;
|
||||
await apiClient.post('/api/settings/update', {
|
||||
userid: userid,
|
||||
settingId: settingId,
|
||||
value: value
|
||||
});
|
||||
console.log('Setting updated:', settingId, value);
|
||||
} catch (err) {
|
||||
console.error('Error updating setting:', err);
|
||||
}
|
||||
},
|
||||
languagesList() {
|
||||
return [
|
||||
{ value: 'en', captionTr: 'settings.personal.languages.en' },
|
||||
{ value: 'de', captionTr: 'settings.personal.languages.de' },
|
||||
];
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
settings: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
48
frontend/src/components/form/CheckboxWidget.vue
Normal file
48
frontend/src/components/form/CheckboxWidget.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<label>
|
||||
<input type="checkbox" :checked="value" @change="updateValue($event.target.checked)" :title="$t(tooltipTr)" />
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "CheckboxWidget",
|
||||
props: {
|
||||
labelTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
tooltipTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue(checked) {
|
||||
this.$emit("input", checked);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
67
frontend/src/components/form/DateInputWidget.vue
Normal file
67
frontend/src/components/form/DateInputWidget.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="date" v-model="internalValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
|
||||
@change="updateValue($event.target.value)" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "DateInputWidget",
|
||||
props: {
|
||||
labelTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
tooltipTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
internalValue: this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.internalValue = newValue;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['language']),
|
||||
},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.internalValue = value;
|
||||
this.$emit("input", value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
label>span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
67
frontend/src/components/form/FloatInputWidget.vue
Normal file
67
frontend/src/components/form/FloatInputWidget.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="number" :value="formattedValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
|
||||
@input="updateValue($event.target.value)" :step="step" />
|
||||
<span v-if="postfix">{{ postfix }}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FloatInputWidget",
|
||||
props: {
|
||||
labelTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
tooltipTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10
|
||||
},
|
||||
decimals: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 2
|
||||
},
|
||||
postfix: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedValue() {
|
||||
return this.value != null ? this.value.toFixed(this.decimals) : '';
|
||||
},
|
||||
step() {
|
||||
return Math.pow(10, -this.decimals);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.$emit("input", parseFloat(value).toFixed(this.decimals));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
45
frontend/src/components/form/InputNumberWidget.vue
Normal file
45
frontend/src/components/form/InputNumberWidget.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="number" :value="value" :title="$t(tooltipTr)" :min="min" :max="max"
|
||||
@input="updateValue($event.target.value)" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "InputNumberWidget",
|
||||
props: {
|
||||
labelTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
tooltipTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.$emit("input", parseFloat(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
65
frontend/src/components/form/InputStringWidget.vue
Normal file
65
frontend/src/components/form/InputStringWidget.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="text" :value="value" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
|
||||
@input="validateAndUpdate($event.target.value)" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "InputStringWidget",
|
||||
props: {
|
||||
labelTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
tooltipTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10
|
||||
},
|
||||
regex: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateAndUpdate(value) {
|
||||
if (this.regex) {
|
||||
const pattern = new RegExp(this.regex);
|
||||
if (pattern.test(value)) {
|
||||
this.updateValue(value);
|
||||
}
|
||||
} else {
|
||||
this.updateValue(value);
|
||||
}
|
||||
},
|
||||
updateValue(value) {
|
||||
this.$emit("input", value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
label > span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
69
frontend/src/components/form/SelectDropdownWidget.vue
Normal file
69
frontend/src/components/form/SelectDropdownWidget.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<select :title="$t(tooltipTr)" :value="value" @change="updateValue($event.target.value)">
|
||||
<option v-if="allowNone" :value="noneValue">{{ $t('none') }}</option>
|
||||
<option v-for="item in list" :key="item.value" :value="item.value">
|
||||
{{ item.captionTr ? $t(item.captionTr) : item.caption }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SelectDropdownWidget",
|
||||
props: {
|
||||
labelTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
tooltipTr: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allowNone: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
noneValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.$emit("input", value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
label>span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
select {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
@@ -15,12 +15,8 @@
|
||||
<div>
|
||||
<label>{{ $t("register.repeatPassword") }}<input type="password" v-model="repeatPassword" /></label>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("register.language") }}<select v-model="language">
|
||||
<option value="en">{{ $t("register.languages.en") }}</option>
|
||||
<option value="de">{{ $t("register.languages.de") }}</option>
|
||||
</select></label>
|
||||
</div>
|
||||
<SelectDropdownWidget labelTr="settings.personal.label.language" :v-model="language"
|
||||
tooltipTr="settings.personal.tooltip.language" :list="languages" :value="language" />
|
||||
</div>
|
||||
<ErrorDialog ref="errorDialog" />
|
||||
</DialogWidget>
|
||||
@@ -31,12 +27,14 @@ import { mapActions } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue';
|
||||
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget.vue';
|
||||
|
||||
export default {
|
||||
name: 'RegisterDialog',
|
||||
components: {
|
||||
DialogWidget,
|
||||
ErrorDialog,
|
||||
SelectDropdownWidget,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -44,7 +42,8 @@ export default {
|
||||
username: '',
|
||||
password: '',
|
||||
repeatPassword: '',
|
||||
language: this.getBrowserLanguage(),
|
||||
language: null,
|
||||
languages: [],
|
||||
buttons: [
|
||||
{ text: 'register.close', action: 'close' },
|
||||
{ text: 'register.register', action: 'register', disabled: !this.canRegister }
|
||||
@@ -61,15 +60,22 @@ export default {
|
||||
this.buttons[1].disabled = !newValue;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getLanguages();
|
||||
await this.getBrowserLanguage();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login']),
|
||||
getBrowserLanguage() {
|
||||
async getBrowserLanguage() {
|
||||
const browserLanguage = navigator.language || navigator.languages[0];
|
||||
let short = '';
|
||||
if (browserLanguage.startsWith('de')) {
|
||||
return 'de';
|
||||
short = 'de';
|
||||
} else {
|
||||
return 'en';
|
||||
short = 'en';
|
||||
}
|
||||
const response = await apiClient.post('/api/settings/getparamvalueid', { paramValue: short });
|
||||
this.language = response.data.paramValueId;
|
||||
},
|
||||
open() {
|
||||
this.$refs.dialog.open();
|
||||
@@ -93,7 +99,6 @@ export default {
|
||||
});
|
||||
|
||||
if (response.status === 201) {
|
||||
console.log(response.data);
|
||||
this.login(response.data);
|
||||
this.$refs.dialog.close();
|
||||
this.$router.push('/activate');
|
||||
@@ -108,6 +113,16 @@ export default {
|
||||
this.$refs.errorDialog.open('tr:register.' + error.response.data.error);
|
||||
}
|
||||
}
|
||||
},
|
||||
async getLanguages() {
|
||||
try {
|
||||
const response = await apiClient.post('/api/settings/getparamvalues', {
|
||||
type: 'language'
|
||||
});
|
||||
this.languages = response.data.map(item => { return { value: item.id, captionTr: `settings.personal.language.${item.name}` } });
|
||||
} catch (err) {
|
||||
console.error('Error loading languages:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import enChat from './locales/en/chat.json';
|
||||
import enRegister from './locales/en/register.json';
|
||||
import enError from './locales/en/error.json';
|
||||
import enActivate from './locales/en/activate.json';
|
||||
import enSettings from './locales/en/settings.json';
|
||||
|
||||
import deGeneral from './locales/de/general.json';
|
||||
import deHeader from './locales/de/header.json';
|
||||
@@ -18,6 +19,7 @@ import deChat from './locales/de/chat.json';
|
||||
import deRegister from './locales/de/register.json';
|
||||
import deError from './locales/de/error.json';
|
||||
import deActivate from './locales/de/activate.json';
|
||||
import deSettings from './locales/de/settings.json';
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
@@ -29,6 +31,7 @@ const messages = {
|
||||
...enRegister,
|
||||
...enError,
|
||||
...enActivate,
|
||||
...enSettings,
|
||||
},
|
||||
de: {
|
||||
...deGeneral,
|
||||
@@ -39,6 +42,7 @@ const messages = {
|
||||
...deRegister,
|
||||
...deError,
|
||||
...deActivate,
|
||||
...deSettings,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
"userrights": "Benutzerrechte",
|
||||
"interrests": "Interessen",
|
||||
"falukant": "Falukant"
|
||||
},
|
||||
"m-friends": {
|
||||
"manageFriends": "Freunde verwalten"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
frontend/src/i18n/locales/de/settings.json
Normal file
32
frontend/src/i18n/locales/de/settings.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"settings": {
|
||||
"personal": {
|
||||
"title": "Persönliche Daten",
|
||||
"label": {
|
||||
"language": "Sprache",
|
||||
"birthdate": "Geburtsdatum",
|
||||
"gender": "Geschlecht",
|
||||
"town": "Stadt",
|
||||
"zip": "PLZ"
|
||||
},
|
||||
"tooltip": {
|
||||
"language": "Sprache",
|
||||
"birthdate": "Geburtsdatum",
|
||||
"gender": "Geschlecht",
|
||||
"town": "Stadt",
|
||||
"zip": "PLZ"
|
||||
},
|
||||
"gender": {
|
||||
"male": "Männlich",
|
||||
"female": "Weiblich",
|
||||
"transmale": "Trans-Frau",
|
||||
"transfemale": "Trans-Mann",
|
||||
"nonbinary": "Nonbinär"
|
||||
},
|
||||
"language": {
|
||||
"de": "Deutsch",
|
||||
"en": "Englisch"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
frontend/src/i18n/locales/en/settings.json
Normal file
3
frontend/src/i18n/locales/en/settings.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import i18n from './i18n';
|
||||
|
||||
function getBrowserLanguage() {
|
||||
const browserLanguage = navigator.language || navigator.languages[0];
|
||||
console.log(browserLanguage);
|
||||
if (browserLanguage.startsWith('de')) {
|
||||
return 'de';
|
||||
} else {
|
||||
|
||||
@@ -11,12 +11,11 @@ const store = createStore({
|
||||
menu: [],
|
||||
},
|
||||
mutations: {
|
||||
dologin(state, user) {
|
||||
async dologin(state, user) {
|
||||
state.isLoggedIn = true;
|
||||
state.user = user;
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
console.log(state.user);
|
||||
},
|
||||
dologout(state) {
|
||||
state.isLoggedIn = false;
|
||||
@@ -45,7 +44,7 @@ const store = createStore({
|
||||
},
|
||||
actions: {
|
||||
async login({ commit, dispatch }, user) {
|
||||
commit('dologin', user);
|
||||
await commit('dologin', user);
|
||||
await dispatch('loadMenu');
|
||||
dispatch('startMenuReload');
|
||||
},
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
Persönliche Einstellungen
|
||||
</div>
|
||||
<SettingsWidget :settingsType="'personal'" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettingsWidget from '@/components/SettingsWidget.vue';
|
||||
|
||||
export default {
|
||||
name: 'PersonalSettingsView',
|
||||
components: {
|
||||
SettingsWidget,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user