1
0

feat: migrate auth store to pina (#2398)

Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2398
Reviewed-by: konrad <k@knt.li>
This commit is contained in:
konrad
2022-09-29 11:20:22 +00:00
44 changed files with 349 additions and 276 deletions

View File

@ -74,16 +74,18 @@ import {useDateTimeSalutation} from '@/composables/useDateTimeSalutation'
import {useListStore} from '@/stores/lists'
import {useConfigStore} from '@/stores/config'
import {useNamespaceStore} from '@/stores/namespaces'
import {useAuthStore} from '@/stores/auth'
const welcome = useDateTimeSalutation()
const store = useStore()
const authStore = useAuthStore()
const configStore = useConfigStore()
const namespaceStore = useNamespaceStore()
const listStore = useListStore()
const listHistory = computed(() => {
// If we don't check this, it tries to load the list background right after logging out
if(!store.state.auth.authenticated) {
if(!authStore.authenticated) {
return []
}
@ -93,13 +95,13 @@ const listHistory = computed(() => {
})
const migratorsEnabled = computed(() => configStore.availableMigrators?.length > 0)
const userInfo = computed(() => store.state.auth.info)
const userInfo = computed(() => authStore.info)
const hasTasks = computed(() => store.state.hasTasks)
const defaultListId = computed(() => store.state.auth.settings.defaultListId)
const defaultListId = computed(() => authStore.settings.defaultListId)
const defaultNamespaceId = computed(() => namespaceStore.namespaces?.[0]?.id || 0)
const hasLists = computed(() => namespaceStore.namespaces?.[0]?.lists.length > 0)
const loading = computed(() => store.state.loading && store.state.loadingModule === 'tasks')
const deletionScheduledAt = computed(() => parseDateOrNull(store.state.auth.info?.deletionScheduledAt))
const deletionScheduledAt = computed(() => parseDateOrNull(authStore.info?.deletionScheduledAt))
// This is to reload the tasks list after adding a new task through the global task add.
// FIXME: Should use vuex (somehow?)

View File

@ -119,10 +119,10 @@ import ColorPicker from '@/components/input/colorPicker.vue'
import LabelModel from '@/models/label'
import type {ILabel} from '@/modelTypes/ILabel'
import {useAuthStore} from '@/stores/auth'
import {useLabelStore} from '@/stores/labels'
import { useTitle } from '@/composables/useTitle'
import { useStore } from '@/store'
const {t} = useI18n({useScope: 'global'})
@ -134,25 +134,23 @@ const labelToDelete = ref<ILabel>(null)
useTitle(() => t('label.title'))
const store = useStore()
const userInfo = computed(() => store.state.auth.info)
const authStore = useAuthStore()
const userInfo = computed(() => authStore.info)
const labelStore = useLabelStore()
labelStore.loadAllLabels()
// Alphabetically sort the labels
const labels = computed(() => Object.values(labelStore.labels).sort((f, s) => f.title > s.title ? 1 : -1))
const loading = computed(() =>labelStore.isLoading)
const loading = computed(() => labelStore.isLoading)
function deleteLabel(label: ILabel) {
showDeleteModal.value = false
isLabelEdit.value = false
const labelStore = useLabelStore()
return labelStore.deleteLabel(label)
}
function editLabelSubmit() {
const labelStore = useLabelStore()
return labelStore.updateLabel(labelEditLabel.value)
}

View File

@ -67,9 +67,9 @@
<script setup lang="ts">
import {ref, computed} from 'vue'
import flatPickr from 'vue-flatpickr-component'
import {useI18n} from 'vue-i18n'
import {useStore} from '@/store'
import {useAuthStore} from '@/stores/auth'
import ListWrapper from './ListWrapper.vue'
import GanttChart from '@/components/tasks/gantt-component.vue'
@ -92,14 +92,14 @@ const dateFrom = ref(new Date((new Date()).setDate(now.value.getDate() - 15)))
const dateTo = ref(new Date((new Date()).setDate(now.value.getDate() + 30)))
const {t} = useI18n({useScope: 'global'})
const store = useStore()
const authStore = useAuthStore()
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatShort'),
altInput: true,
dateFormat: 'Y-m-d',
enableTime: false,
locale: {
firstDayOfWeek: store.state.auth.settings.weekStart,
firstDayOfWeek: authStore.settings.weekStart,
},
}))
</script>

View File

@ -41,6 +41,7 @@ import CreateEdit from '@/components/misc/create-edit.vue'
import LinkSharing from '@/components/sharing/linkSharing.vue'
import userTeam from '@/components/sharing/userTeam.vue'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
const {t} = useI18n({useScope: 'global'})
@ -52,10 +53,11 @@ const title = computed(() => list.value?.title
useTitle(title)
const store = useStore()
const authStore = useAuthStore()
const configStore = useConfigStore()
const linkSharingEnabled = computed(() => configStore.linkSharingEnabled)
const userIsAdmin = computed(() => 'owner' in list.value && list.value.owner.id === store.state.auth.info.id)
const userIsAdmin = computed(() => 'owner' in list.value && list.value.owner.id === authStore.info.id)
async function loadList(listId: number) {
const listService = new ListService()

View File

@ -26,20 +26,21 @@ export default { name: 'namespace-setting-share' }
<script lang="ts" setup>
import {ref, computed, watchEffect} from 'vue'
import {useStore} from '@/store'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@vueuse/core'
import NamespaceService from '@/services/namespace'
import NamespaceModel from '@/models/namespace'
import CreateEdit from '@/components/misc/create-edit.vue'
import manageSharing from '@/components/sharing/userTeam.vue'
import {useTitle} from '@/composables/useTitle'
import {useAuthStore} from '@/stores/auth'
import type {INamespace} from '@/modelTypes/INamespace'
const {t} = useI18n({useScope: 'global'})
const namespace = ref()
const namespace = ref<INamespace>()
const title = computed(() => namespace.value?.title
? t('namespace.share.title', { namespace: namespace.value.title })
@ -47,8 +48,8 @@ const title = computed(() => namespace.value?.title
)
useTitle(title)
const store = useStore()
const userIsAdmin = computed(() => 'owner' in namespace.value && namespace.value.owner.id === store.state.auth.info.id)
const authStore = useAuthStore()
const userIsAdmin = computed(() => 'owner' in namespace.value && namespace.value.owner.id === authStore.info.id)
async function loadNamespace(namespaceId: number) {
if (!namespaceId) return

View File

@ -42,12 +42,14 @@ import {useTitle} from '@vueuse/core'
import Message from '@/components/misc/message.vue'
import {LOGO_VISIBLE} from '@/store/mutation-types'
import {LIST_VIEWS, type ListView} from '@/types/ListView'
import {useAuthStore} from '@/stores/auth'
const {t} = useI18n({useScope: 'global'})
useTitle(t('sharing.authenticating'))
function useAuth() {
const store = useStore()
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
@ -56,7 +58,7 @@ function useAuth() {
const errorMessage = ref('')
const password = ref('')
const authLinkShare = computed(() => store.getters['auth/authLinkShare'])
const authLinkShare = computed(() => authStore.authLinkShare)
async function authenticate() {
authenticateWithPassword.value = false
@ -72,7 +74,7 @@ function useAuth() {
loading.value = true
try {
const {list_id: listId} = await store.dispatch('auth/linkShareAuth', {
const {list_id: listId} = await authStore.linkShareAuth({
hash: route.params.share,
password: password.value,
})

View File

@ -59,8 +59,10 @@ import {DATE_RANGES} from '@/components/date/dateRanges'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
import LlamaCool from '@/assets/llama-cool.svg?component'
import type {ITask} from '@/modelTypes/ITask'
import {useAuthStore} from '@/stores/auth'
const store = useStore()
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
const {t} = useI18n({useScope: 'global'})
@ -104,7 +106,7 @@ const pageTitle = computed(() => {
})
})
const hasTasks = computed(() => tasks.value && tasks.value.length > 0)
const userAuthenticated = computed(() => store.state.auth.authenticated)
const userAuthenticated = computed(() => authStore.authenticated)
const loading = computed(() => store.state[LOADING] && store.state[LOADING_MODULE] === 'tasks')
interface dateStrings {

View File

@ -85,7 +85,7 @@
<table class="table has-actions is-striped is-hoverable is-fullwidth">
<tbody>
<tr :key="m.id" v-for="m in team?.members">
<td>{{ m.getDisplayName() }}</td>
<td>{{ getDisplayName(m) }}</td>
<td>
<template v-if="m.id === userInfo.id">
<b class="is-success">You</b>
@ -163,9 +163,10 @@
<script lang="ts" setup>
import {computed, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import {useRoute, useRouter} from 'vue-router'
import Editor from '@/components/input/AsyncEditor'
import {useStore} from '@/store'
import Multiselect from '@/components/input/multiselect.vue'
import TeamService from '@/services/team'
import TeamMemberService from '@/services/teamMember'
@ -173,15 +174,16 @@ import UserService from '@/services/user'
import {RIGHTS as Rights} from '@/constants/rights'
import Multiselect from '@/components/input/multiselect.vue'
import {useRoute, useRouter} from 'vue-router'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {getDisplayName} from '@/models/user'
import {useAuthStore} from '@/stores/auth'
import type {ITeam} from '@/modelTypes/ITeam'
import type {IUser} from '@/modelTypes/IUser'
import type {ITeamMember} from '@/modelTypes/ITeamMember'
const store = useStore()
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
const {t} = useI18n({useScope: 'global'})
@ -193,7 +195,7 @@ const userIsAdmin = computed(() => {
team.value.maxRight > Rights.READ
)
})
const userInfo = computed(() => store.state.auth.info)
const userInfo = computed(() => authStore.info)
const teamService = ref<TeamService>(new TeamService())
const teamMemberService = ref<TeamMemberService>(new TeamMemberService())

View File

@ -38,14 +38,15 @@
<script setup lang="ts">
import {ref, computed, reactive} from 'vue'
import DataExportService from '@/services/dataExport'
import {store} from '@/store'
import {useAuthStore} from '@/stores/auth'
const dataExportService = reactive(new DataExportService())
const password = ref('')
const errPasswordRequired = ref(false)
const passwordInput = ref(null)
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
const authStore = useAuthStore()
const isLocalUser = computed(() => authStore.info?.isLocalUser)
function download() {
if (password.value === '' && isLocalUser.value) {

View File

@ -116,6 +116,7 @@ import {getLastVisited, clearLastVisited} from '../../helpers/saveLastVisited'
import Password from '@/components/input/password.vue'
import { setTitle } from '@/helpers/setTitle'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
export default defineComponent({
components: {
@ -173,8 +174,11 @@ export default defineComponent({
},
...mapStateVuex({
loading: LOADING,
needsTotpPasscode: state => state.auth.needsTotpPasscode,
authenticated: state => state.auth.authenticated,
}),
...mapState(useAuthStore, {
needsTotpPasscode: state => state.needsTotpPasscode,
authenticated: state => state.authenticated,
}),
...mapState(useConfigStore, {
@ -224,8 +228,9 @@ export default defineComponent({
}
try {
await this.$store.dispatch('auth/login', credentials)
this.$store.commit('auth/needsTotpPasscode', false)
const authStore = useAuthStore()
await authStore.login(credentials)
authStore.setNeedsTotpPasscode(false)
} catch (e) {
if (e.response?.data.code === 1017 && !this.credentials.totpPasscode) {
return

View File

@ -22,6 +22,7 @@ import {useI18n} from 'vue-i18n'
import {getErrorText} from '@/message'
import Message from '@/components/misc/message.vue'
import {clearLastVisited, getLastVisited} from '@/helpers/saveLastVisited'
import {useAuthStore} from '@/stores/auth'
const {t} = useI18n({useScope: 'global'})
@ -29,6 +30,7 @@ const router = useRouter()
const route = useRoute()
const store = useStore()
const authStore = useAuthStore()
const loading = computed(() => store.state.loading)
const errorMessage = ref('')
@ -65,7 +67,7 @@ async function authenticateWithCode() {
}
try {
await store.dispatch('auth/openIdAuth', {
await authStore.openIdAuth({
provider: route.params.provider,
code: route.query.code,
})

View File

@ -77,11 +77,14 @@ import {store} from '@/store'
import Message from '@/components/misc/message.vue'
import {isEmail} from '@/helpers/isEmail'
import Password from '@/components/input/password.vue'
import {useAuthStore} from '@/stores/auth'
const authStore = useAuthStore()
// FIXME: use the `beforeEnter` hook of vue-router
// Check if the user is already logged in, if so, redirect them to the homepage
onBeforeMount(() => {
if (store.state.auth.authenticated) {
if (authStore.authenticated) {
router.push({name: 'home'})
}
})
@ -126,7 +129,7 @@ async function submit() {
}
try {
await store.dispatch('auth/register', toRaw(credentials))
await authStore.register(toRaw(credentials))
} catch (e) {
errorMessage.value = e.message
}

View File

@ -19,19 +19,21 @@
<script setup lang="ts">
import {computed} from 'vue'
import { store } from '@/store'
import { useI18n } from 'vue-i18n'
import { useTitle } from '@/composables/useTitle'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
const { t } = useI18n({useScope: 'global'})
useTitle(() => t('user.settings.title'))
const configStore = useConfigStore()
const authStore = useAuthStore()
const totpEnabled = computed(() => configStore.totpEnabled)
const caldavEnabled = computed(() => configStore.caldavEnabled)
const migratorsEnabled = computed(() => configStore.migratorsEnabled)
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
const isLocalUser = computed(() => authStore.info?.isLocalUser)
const navigationItems = computed(() => {
const items = [

View File

@ -64,17 +64,17 @@ export default { name: 'user-settings-avatar' }
<script setup lang="ts">
import {computed, ref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from '@/store'
import {Cropper} from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'
import AvatarService from '@/services/avatar'
import AvatarModel from '@/models/avatar'
import { useTitle } from '@/composables/useTitle'
import { success } from '@/message'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
const {t} = useI18n({useScope: 'global'})
const store = useStore()
const authStore = useAuthStore()
const AVATAR_PROVIDERS = computed(() => ({
default: t('misc.default'),
@ -102,7 +102,7 @@ avatarStatus()
async function updateAvatarStatus() {
await avatarService.update(new AvatarModel({avatarProvider: avatarProvider.value}))
success({message: t('user.settings.avatar.statusUpdateSuccess')})
store.commit('auth/reloadAvatar')
authStore.reloadAvatar()
}
const cropper = ref()
@ -121,7 +121,7 @@ async function uploadAvatar() {
const blob = await new Promise(resolve => canvas.toBlob(blob => resolve(blob)))
await avatarService.create(blob)
success({message: t('user.settings.avatar.setSuccess')})
store.commit('auth/reloadAvatar')
authStore.reloadAvatar()
} finally {
loading.value = false
isCropAvatar.value = false

View File

@ -68,7 +68,6 @@
<script lang="ts" setup>
import {computed, ref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from '@/store'
import {CALDAV_DOCS} from '@/urls'
import {useTitle} from '@/composables/useTitle'
@ -80,6 +79,7 @@ import CaldavTokenService from '@/services/caldavToken'
import { formatDateShort } from '@/helpers/time/formatDate'
import type {ICaldavToken} from '@/modelTypes/ICaldavToken'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
const copy = useCopyToClipboard()
@ -105,10 +105,10 @@ async function deleteToken(token: ICaldavToken) {
success(r)
}
const store = useStore()
const authStore = useAuthStore()
const configStore = useConfigStore()
const username = computed(() => store.state.auth.info?.username)
const username = computed(() => authStore.info?.username)
const caldavUrl = computed(() => `${configStore.apiBase}/dav/principals/${username.value}/`)
const caldavEnabled = computed(() => configStore.caldavEnabled)
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
const isLocalUser = computed(() => authStore.info?.isLocalUser)
</script>

View File

@ -44,22 +44,22 @@ export default {name: 'user-settings-data-export'}
<script setup lang="ts">
import {ref, computed, shallowReactive} from 'vue'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import DataExportService from '@/services/dataExport'
import { useTitle } from '@/composables/useTitle'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
const {t} = useI18n({useScope: 'global'})
const store = useStore()
const authStore = useAuthStore()
useTitle(() => `${t('user.export.title')} - ${t('user.settings.title')}`)
const dataExportService = shallowReactive(new DataExportService())
const password = ref('')
const errPasswordRequired = ref(false)
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
const isLocalUser = computed(() => authStore.info?.isLocalUser)
const passwordInput = ref()
async function requestDataExport() {

View File

@ -88,7 +88,6 @@ export default { name: 'user-settings-deletion' }
<script setup lang="ts">
import {ref, shallowReactive, computed} from 'vue'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import AccountDeleteService from '@/services/accountDelete'
@ -96,6 +95,7 @@ import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
import {useConfigStore} from '@/stores/config'
const {t} = useI18n({useScope: 'global'})
@ -105,11 +105,11 @@ const accountDeleteService = shallowReactive(new AccountDeleteService())
const password = ref('')
const errPasswordRequired = ref(false)
const store = useStore()
const authStore = useAuthStore()
const configStore = useConfigStore()
const userDeletionEnabled = computed(() => configStore.userDeletionEnabled)
const deletionScheduledAt = computed(() => parseDateOrNull(store.state.auth.info?.deletionScheduledAt))
const deletionScheduledAt = computed(() => parseDateOrNull(authStore.info?.deletionScheduledAt))
const passwordInput = ref()
async function deleteAccount() {
@ -133,7 +133,7 @@ async function cancelDeletion() {
await accountDeleteService.cancel(password.value)
success({message: t('user.deletion.scheduledCancelSuccess')})
store.dispatch('auth/refreshUserInfo')
authStore.refreshUserInfo()
password.value = ''
}
</script>

View File

@ -43,18 +43,18 @@ export default { name: 'user-settings-update-email' }
<script setup lang="ts">
import {reactive, computed, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from '@/store'
import EmailUpdateService from '@/services/emailUpdate'
import EmailUpdateModel from '@/models/emailUpdate'
import {success} from '@/message'
import {useTitle} from '@/composables/useTitle'
import {useAuthStore} from '@/stores/auth'
const {t} = useI18n({useScope: 'global'})
useTitle(() => `${t('user.settings.updateEmailTitle')} - ${t('user.settings.title')}`)
const store = useStore()
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
const authStore = useAuthStore()
const isLocalUser = computed(() => authStore.info?.isLocalUser)
const emailUpdate = reactive(new EmailUpdateModel())
const emailUpdateService = shallowReactive(new EmailUpdateService())

View File

@ -149,6 +149,7 @@
<script lang="ts">
import {defineComponent} from 'vue'
import { useListStore } from '@/stores/lists'
import { useAuthStore } from '@/stores/auth'
export default defineComponent({
name: 'user-settings-general',
@ -227,7 +228,8 @@ const playSoundWhenDone = ref(getPlaySoundWhenDoneSetting())
const quickAddMagicMode = ref(getQuickAddMagicMode())
const store = useStore()
const settings = ref({...store.state.auth.settings})
const authStore = useAuthStore()
const settings = ref({...authStore.settings})
const id = ref(createRandomID())
const availableLanguageOptions = ref(
Object.entries(availableLanguages)
@ -236,13 +238,13 @@ const availableLanguageOptions = ref(
)
watch(
() => store.state.auth.settings,
() => authStore.settings,
() => {
// Only setting if we don't have values set yet to avoid overriding edited values
if (!objectIsEmpty(settings.value)) {
return
}
settings.value = {...store.state.auth.settings}
settings.value = {...authStore.settings}
},
{immediate: true},
)
@ -265,7 +267,7 @@ async function updateSettings() {
localStorage.setItem(playSoundWhenDoneKey, playSoundWhenDone.value ? 'true' : 'false')
setQuickAddMagicMode(quickAddMagicMode.value)
await store.dispatch('auth/saveUserSettings', {
await authStore.saveUserSettings({
settings: {...settings.value},
})
}

View File

@ -57,24 +57,24 @@ export default {name: 'user-settings-password-update'}
<script setup lang="ts">
import {ref, reactive, shallowReactive, computed} from 'vue'
import { useI18n } from 'vue-i18n'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import PasswordUpdateService from '@/services/passwordUpdateService'
import PasswordUpdateModel from '@/models/passwordUpdate'
import {useTitle} from '@/composables/useTitle'
import {success, error} from '@/message'
import {useAuthStore} from '@/stores/auth'
const passwordUpdateService = shallowReactive(new PasswordUpdateService())
const passwordUpdate = reactive(new PasswordUpdateModel())
const passwordConfirm = ref('')
const {t} = useI18n({useScope: 'global'})
const store = useStore()
useTitle(() => `${t('user.settings.newPasswordTitle')} - ${t('user.settings.title')}`)
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
const authStore = useAuthStore()
const isLocalUser = computed(() => authStore.info?.isLocalUser)
async function updatePassword() {
if (passwordConfirm.value !== passwordUpdate.newPassword) {