feat: save and restore the user language on the server (#1181)

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1181
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
This commit is contained in:
konrad 2021-12-30 20:20:45 +00:00
parent 0befa58908
commit 4a7d2d8414
2 changed files with 67 additions and 39 deletions

View File

@ -1,7 +1,12 @@
import {HTTPFactory} from '@/http-common' import {HTTPFactory} from '@/http-common'
import {getCurrentLanguage, saveLanguage} from '@/i18n'
import {LOADING} from '../mutation-types' import {LOADING} from '../mutation-types'
import UserModel from '../../models/user' import UserModel from '@/models/user'
import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth' import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setLoading} from '@/store/helper'
import {i18n} from '@/i18n'
import {success} from '@/message'
const AUTH_TYPES = { const AUTH_TYPES = {
'UNKNOWN': 0, 'UNKNOWN': 0,
@ -98,10 +103,10 @@ export default {
const response = await HTTP.post('login', data) const response = await HTTP.post('login', data)
// Save the token to local storage for later use // Save the token to local storage for later use
saveToken(response.data.token, true) saveToken(response.data.token, true)
// Tell others the user is autheticated // Tell others the user is autheticated
ctx.dispatch('checkAuth') ctx.dispatch('checkAuth')
} catch(e) { } catch (e) {
if ( if (
e.response && e.response &&
e.response.data.code === 1017 && e.response.data.code === 1017 &&
@ -124,7 +129,7 @@ export default {
try { try {
await HTTP.post('register', credentials) await HTTP.post('register', credentials)
return ctx.dispatch('login', credentials) return ctx.dispatch('login', credentials)
} catch(e) { } catch (e) {
if (e.response?.data?.message) { if (e.response?.data?.message) {
throw e.response.data throw e.response.data
} }
@ -149,7 +154,7 @@ export default {
const response = await HTTP.post(`/auth/openid/${provider}/callback`, data) const response = await HTTP.post(`/auth/openid/${provider}/callback`, data)
// Save the token to local storage for later use // Save the token to local storage for later use
saveToken(response.data.token, true) saveToken(response.data.token, true)
// Tell others the user is autheticated // Tell others the user is autheticated
ctx.dispatch('checkAuth') ctx.dispatch('checkAuth')
} finally { } finally {
@ -200,7 +205,7 @@ export default {
} }
}, },
async refreshUserInfo(ctx) { async refreshUserInfo({state, commit, dispatch}) {
const jwt = getToken() const jwt = getToken()
if (!jwt) { if (!jwt) {
return return
@ -208,22 +213,53 @@ export default {
const HTTP = HTTPFactory() const HTTP = HTTPFactory()
try { try {
const response = await HTTP.get('user', { const response = await HTTP.get('user', {
headers: { headers: {
Authorization: `Bearer ${jwt}`, Authorization: `Bearer ${jwt}`,
}, },
}) })
const info = new UserModel(response.data) const info = new UserModel(response.data)
info.type = ctx.state.info.type info.type = state.info.type
info.email = ctx.state.info.email info.email = state.info.email
info.exp = ctx.state.info.exp info.exp = state.info.exp
ctx.commit('info', info) commit('info', info)
ctx.commit('lastUserRefresh') commit('lastUserRefresh')
if (typeof info.settings.language !== 'undefined') {
// save current language
await dispatch('saveUserSettings', {
settings: {
...state.settings,
language: getCurrentLanguage(),
},
showMessage: false,
})
}
return info return info
} catch(e) { } catch (e) {
throw new Error('Error while refreshing user info:', { cause: e }) throw new Error('Error while refreshing user info:', {cause: e})
}
},
async saveUserSettings(ctx, payload) {
const {settings} = payload
const showMessage = payload.showMessage ?? true
const userSettingsService = new UserSettingsService()
const cancel = setLoading(ctx, 'general-settings')
try {
saveLanguage(settings.language)
await userSettingsService.update(settings)
ctx.commit('setUserSettings', {...settings})
if (showMessage) {
success({message: i18n.global.t('user.settings.general.savedSuccess')})
}
} catch (e) {
throw new Error('Error while saving user settings:', {cause: e})
} finally {
cancel()
} }
}, },
@ -240,7 +276,7 @@ export default {
try { try {
await refreshToken(!ctx.state.isLinkShareAuth) await refreshToken(!ctx.state.isLinkShareAuth)
ctx.dispatch('checkAuth') ctx.dispatch('checkAuth')
} catch(e) { } catch (e) {
// Don't logout on network errors as the user would then get logged out if they don't have // Don't logout on network errors as the user would then get logged out if they don't have
// internet for a short period of time - such as when the laptop is still reconnecting // internet for a short period of time - such as when the laptop is still reconnecting
if (e?.request?.status) { if (e?.request?.status) {

View File

@ -1,5 +1,5 @@
<template> <template>
<card :title="$t('user.settings.general.title')" class="general-settings" :loading="userSettingsService.loading"> <card :title="$t('user.settings.general.title')" class="general-settings" :loading="loading">
<div class="field"> <div class="field">
<label class="label" :for="`newName${id}`">{{ $t('user.settings.general.name') }}</label> <label class="label" :for="`newName${id}`">{{ $t('user.settings.general.name') }}</label>
<div class="control"> <div class="control">
@ -67,8 +67,8 @@
{{ $t('user.settings.general.language') }} {{ $t('user.settings.general.language') }}
</span> </span>
<div class="select ml-2"> <div class="select ml-2">
<select v-model="language"> <select v-model="settings.language">
<option :value="lang.code" v-for="lang in availableLanguages" :key="lang.code">{{ <option :value="lang.code" v-for="lang in availableLanguageOptions" :key="lang.code">{{
lang.title lang.title
}} }}
</option> </option>
@ -107,7 +107,7 @@
</div> </div>
<x-button <x-button
:loading="userSettingsService.loading" :loading="loading"
@click="updateSettings()" @click="updateSettings()"
class="is-fullwidth mt-4" class="is-fullwidth mt-4"
> >
@ -120,14 +120,12 @@
import {computed, watch} from 'vue' import {computed, watch} from 'vue'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {playSoundWhenDoneKey} from '@/helpers/playPop' import {playSoundWhenDoneKey, playPop} from '@/helpers/playPop'
import {availableLanguages, saveLanguage, getCurrentLanguage} from '@/i18n' import {availableLanguages} from '@/i18n'
import {getQuickAddMagicMode, setQuickAddMagicMode} from '@/helpers/quickAddMagicMode' import {getQuickAddMagicMode, setQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import UserSettingsService from '@/services/userSettings'
import {PrefixMode} from '@/modules/parseTaskText' import {PrefixMode} from '@/modules/parseTaskText'
import ListSearch from '@/components/tasks/partials/listSearch' import ListSearch from '@/components/tasks/partials/listSearch'
import {createRandomID} from '@/helpers/randomId' import {createRandomID} from '@/helpers/randomId'
import {playPop} from '@/helpers/playPop'
import {useColorScheme} from '@/composables/useColorScheme' import {useColorScheme} from '@/composables/useColorScheme'
import {success} from '@/message' import {success} from '@/message'
@ -165,23 +163,19 @@ export default {
data() { data() {
return { return {
playSoundWhenDone: getPlaySoundWhenDoneSetting(), playSoundWhenDone: getPlaySoundWhenDoneSetting(),
language: getCurrentLanguage(),
quickAddMagicMode: getQuickAddMagicMode(), quickAddMagicMode: getQuickAddMagicMode(),
quickAddMagicPrefixes: PrefixMode, quickAddMagicPrefixes: PrefixMode,
userSettingsService: new UserSettingsService(),
settings: {...this.$store.state.auth.settings}, settings: {...this.$store.state.auth.settings},
id: createRandomID(), id: createRandomID(),
availableLanguageOptions: Object.entries(availableLanguages)
.map(l => ({code: l[0], title: l[1]}))
.sort((a, b) => a.title.localeCompare(b.title)),
} }
}, },
components: { components: {
ListSearch, ListSearch,
}, },
computed: { computed: {
availableLanguages() {
return Object.entries(availableLanguages)
.map(l => ({code: l[0], title: l[1]}))
.sort((a, b) => a.title.localeCompare(b.title))
},
defaultList: { defaultList: {
get() { get() {
return this.$store.getters['lists/getListById'](this.settings.defaultListId) return this.$store.getters['lists/getListById'](this.settings.defaultListId)
@ -190,6 +184,9 @@ export default {
this.settings.defaultListId = l ? l.id : DEFAULT_LIST_ID this.settings.defaultListId = l ? l.id : DEFAULT_LIST_ID
}, },
}, },
loading() {
return this.$store.state.loading && this.$store.state.loadingModule === 'general-settings'
},
}, },
setup() { setup() {
@ -211,16 +208,11 @@ export default {
methods: { methods: {
async updateSettings() { async updateSettings() {
localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone) localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone)
saveLanguage(this.language)
setQuickAddMagicMode(this.quickAddMagicMode) setQuickAddMagicMode(this.quickAddMagicMode)
const settings = { await this.$store.dispatch('auth/saveUserSettings', {
...this.settings, settings: {...this.settings},
} })
await this.userSettingsService.update(settings)
this.$store.commit('auth/setUserSettings', settings)
this.$message.success({message: this.$t('user.settings.general.savedSuccess')})
}, },
}, },
} }