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

@ -71,7 +71,6 @@
<script lang="ts" setup>
import {computed, ref, watch} from 'vue'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component'
@ -81,8 +80,9 @@ import Popup from '@/components/misc/popup.vue'
import {DATE_RANGES} from '@/components/date/dateRanges'
import BaseButton from '@/components/base/BaseButton.vue'
import DatemathHelp from '@/components/date/datemathHelp.vue'
import {useAuthStore} from '@/stores/auth'
const store = useStore()
const authStore = useAuthStore()
const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['update:modelValue'])
@ -93,7 +93,7 @@ const props = defineProps({
})
// FIXME: This seems to always contain the default value - that breaks the picker
const weekStart = computed<number>(() => store.state.auth.settings.weekStart ?? 0)
const weekStart = computed(() => authStore.settings.weekStart ?? 0)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,

View File

@ -93,7 +93,6 @@
<script setup lang="ts">
import {ref, computed, onMounted, nextTick} from 'vue'
import {useStore} from '@/store'
import {useRouter} from 'vue-router'
import {QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
import {RIGHTS as Rights} from '@/constants/rights'
@ -109,12 +108,14 @@ import MenuButton from '@/components/home/MenuButton.vue'
import {getListTitle} from '@/helpers/getListTitle'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
const store = useStore()
const authStore = useAuthStore()
const configStore = useConfigStore()
const userInfo = computed(() => store.state.auth.info)
const userAvatar = computed(() => store.state.auth.avatarUrl)
const userInfo = computed(() => authStore.info)
const userAvatar = computed(() => authStore.avatarUrl)
const currentList = computed(() => store.state.currentList)
const background = computed(() => store.state.background)
const imprintUrl = computed(() => configStore.legal.imprintUrl)
@ -134,11 +135,8 @@ onMounted(async () => {
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
})
const router = useRouter()
function logout() {
store.dispatch('auth/logout')
router.push({name: 'user.login'})
authStore.logout()
}
function openQuickActions() {

View File

@ -70,6 +70,7 @@ import {useLabelStore} from '@/stores/labels'
import Navigation from '@/components/home/navigation.vue'
import QuickActions from '@/components/quick-actions/quick-actions.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import {useAuthStore} from '@/stores/auth'
function useRouteWithModal() {
const router = useRouter()
@ -165,13 +166,15 @@ watch(() => route.name as string, (routeName) => {
function useRenewTokenOnFocus() {
const router = useRouter()
const authStore = useAuthStore()
const userInfo = computed(() => store.state.auth.info)
const authenticated = computed(() => store.state.auth.authenticated)
const userInfo = computed(() => authStore.info)
const authenticated = computed(() => authStore.authenticated)
// Try renewing the token every time vikunja is loaded initially
// (When opening the browser the focus event is not fired)
store.dispatch('auth/renewToken')
authStore.renewToken()
// Check if the token is still valid if the window gets focus again to maybe renew it
useEventListener('focus', () => {
@ -184,14 +187,14 @@ function useRenewTokenOnFocus() {
// If the token expiry is negative, it is already expired and we have no choice but to redirect
// the user to the login page
if (expiresIn < 0) {
store.dispatch('auth/checkAuth')
authStore.checkAuth()
router.push({name: 'user.login'})
return
}
// Check if the token is valid for less than 60 hours and renew if thats the case
if (expiresIn < 60 * 3600) {
store.dispatch('auth/renewToken')
authStore.renewToken()
console.debug('renewed token')
}
})

View File

@ -102,6 +102,8 @@ import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {mapState} from 'pinia'
import {useAuthStore} from '@/stores/auth'
export default defineComponent({
name: 'datepicker',
@ -145,6 +147,9 @@ export default defineComponent({
},
},
computed: {
...mapState(useAuthStore, {
weekStart: (state) => state.settings.weekStart,
}),
flatPickerConfig() {
return {
altFormat: this.$t('date.altFormatLong'),
@ -154,7 +159,7 @@ export default defineComponent({
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
firstDayOfWeek: this.weekStart,
},
}
},

View File

@ -2,34 +2,39 @@
<div :class="{'is-inline': isInline}" class="user">
<img
:height="avatarSize"
:src="user.getAvatarUrl(avatarSize)"
:src="getAvatarUrl(user, avatarSize)"
:width="avatarSize"
alt=""
class="avatar"
v-tooltip="user.getDisplayName()"/>
<span class="username" v-if="showUsername">{{ user.getDisplayName() }}</span>
v-tooltip="getDisplayName(user)"/>
<span class="username" v-if="showUsername">{{ getDisplayName(user) }}</span>
</div>
</template>
<script lang="ts" setup>
import type {PropType} from 'vue'
import {getAvatarUrl, getDisplayName} from '@/models/user'
import type {IUser} from '@/modelTypes/IUser'
defineProps({
user: {
type: Object as PropType<IUser>,
required: true,
type: Object,
},
showUsername: {
required: false,
type: Boolean,
required: false,
default: true,
},
avatarSize: {
required: false,
type: Number,
required: false,
default: 50,
},
isInline: {
required: false,
type: Boolean,
required: false,
default: false,
},
})

View File

@ -24,7 +24,7 @@
<div class="detail">
<div>
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
{{ n.notification.doer.getDisplayName() }}
{{ getDisplayName(n.notification.doer) }}
</span>
<BaseButton @click="() => to(n, index)()">
{{ n.toText(userInfo) }}
@ -48,19 +48,20 @@
<script lang="ts" setup>
import {computed, onMounted, onUnmounted, ref} from 'vue'
import {useRouter} from 'vue-router'
import NotificationService from '@/services/notification'
import BaseButton from '@/components/base/BaseButton.vue'
import User from '@/components/misc/user.vue'
import { NOTIFICATION_NAMES as names, type INotification} from '@/modelTypes/INotification'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {useStore} from '@/store'
import {useRouter} from 'vue-router'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getDisplayName} from '@/models/user'
import {useAuthStore} from '@/stores/auth'
const LOAD_NOTIFICATIONS_INTERVAL = 10000
const store = useStore()
const authStore = useAuthStore()
const router = useRouter()
const allNotifications = ref<INotification[]>([])
@ -73,7 +74,7 @@ const unreadNotifications = computed(() => {
const notifications = computed(() => {
return allNotifications.value ? allNotifications.value.filter(n => n.name !== '') : []
})
const userInfo = computed(() => store.state.auth.info)
const userInfo = computed(() => authStore.info)
let interval: number

View File

@ -93,7 +93,7 @@
<p class="mb-2">
<i18n-t keypath="list.share.links.sharedBy" scope="global">
<strong>{{ s.sharedBy.getDisplayName() }}</strong>
<strong>{{ getDisplayName(s.sharedBy) }}</strong>
</i18n-t>
</p>
@ -201,6 +201,7 @@ import LinkShareService from '@/services/linkShare'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import {success} from '@/message'
import {getDisplayName} from '@/models/user'
import type {ListView} from '@/types/ListView'
import {LIST_VIEWS} from '@/types/ListView'
import {useConfigStore} from '@/stores/config'

View File

@ -28,7 +28,7 @@
<tbody>
<tr :key="s.id" v-for="s in sharables">
<template v-if="shareType === 'user'">
<td>{{ s.getDisplayName() }}</td>
<td>{{ getDisplayName(s) }}</td>
<td>
<template v-if="s.id === userInfo.id">
<b class="is-success">{{ $t('list.share.userTeam.you') }}</b>
@ -139,7 +139,6 @@ export default {name: 'userTeamShare'}
<script setup lang="ts">
import {ref, reactive, computed, shallowReactive, type Ref} from 'vue'
import type {PropType} from 'vue'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import UserNamespaceService from '@/services/userNamespace'
@ -151,7 +150,7 @@ import UserListModel from '@/models/userList'
import type {IUserList} from '@/modelTypes/IUserList'
import UserService from '@/services/user'
import UserModel from '@/models/user'
import UserModel, { getDisplayName } from '@/models/user'
import type {IUser} from '@/modelTypes/IUser'
import TeamNamespaceService from '@/services/teamNamespace'
@ -171,6 +170,7 @@ import {RIGHTS} from '@/constants/rights'
import Multiselect from '@/components/input/multiselect.vue'
import Nothing from '@/components/misc/nothing.vue'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
const props = defineProps({
type: {
@ -208,8 +208,8 @@ const sharables = ref([])
const showDeleteModal = ref(false)
const store = useStore()
const userInfo = computed(() => store.state.auth.info)
const authStore = useAuthStore()
const userInfo = computed(() => authStore.info)
function createShareTypeNameComputed(count: number) {
return computed(() => {
@ -365,7 +365,7 @@ async function toggleType(sharable) {
const found = ref([])
const currentUserId = computed(() => store.state.auth.info.id)
const currentUserId = computed(() => authStore.info.id)
async function find(query: string) {
if (query === '') {
found.value = []

View File

@ -48,6 +48,7 @@ import {tryOnMounted, debouncedWatch, useWindowSize, type MaybeRef} from '@vueus
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
import {useAuthStore} from '@/stores/auth'
function cleanupTitle(title: string) {
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
@ -135,6 +136,7 @@ const newTaskInput = useAutoHeightTextarea(newTaskTitle)
const {t} = useI18n({useScope: 'global'})
const store = useStore()
const authStore = useAuthStore()
const errorMessage = ref('')
@ -168,7 +170,7 @@ async function addTask() {
const task = await store.dispatch('tasks/createNewTask', {
title,
listId: store.state.auth.settings.defaultListId,
listId: authStore.settings.defaultListId,
position: props.defaultPosition,
})
emit('taskAdded', task)

View File

@ -17,7 +17,7 @@
<div :key="c.id" class="media comment" v-for="c in comments">
<figure class="media-left is-hidden-mobile">
<img
:src="c.author.getAvatarUrl(48)"
:src="getAvatarUrl(c.author, 48)"
alt=""
class="image is-avatar"
height="48"
@ -27,13 +27,13 @@
<div class="media-content">
<div class="comment-info">
<img
:src="c.author.getAvatarUrl(20)"
:src="getAvatarUrl(c.author, 20)"
alt=""
class="image is-avatar d-print-none"
height="20"
width="20"
/>
<strong>{{ c.author.getDisplayName() }}</strong>&nbsp;
<strong>{{ getDisplayName(c.author) }}</strong>&nbsp;
<span v-tooltip="formatDateLong(c.created)" class="has-text-grey">
{{ formatDateSince(c.created) }}
</span>
@ -153,7 +153,6 @@
<script setup lang="ts">
import {ref, reactive, computed, shallowReactive, watch, nextTick} from 'vue'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import Editor from '@/components/input/AsyncEditor'
@ -167,7 +166,9 @@ import type {ITask} from '@/modelTypes/ITask'
import {uploadFile} from '@/helpers/attachments'
import {success} from '@/message'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getAvatarUrl, getDisplayName} from '@/models/user'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
const props = defineProps({
taskId: {
@ -180,8 +181,8 @@ const props = defineProps({
})
const {t} = useI18n({useScope: 'global'})
const store = useStore()
const configStore = useConfigStore()
const authStore = useAuthStore()
const comments = ref<ITaskComment[]>([])
@ -196,8 +197,8 @@ const newComment = reactive(new TaskCommentModel())
const saved = ref<ITask['id'] | null>(null)
const saving = ref<ITask['id'] | null>(null)
const userAvatar = computed(() => store.state.auth.info.getAvatarUrl(48))
const currentUserId = computed(() => store.state.auth.info.id)
const userAvatar = computed(() => getAvatarUrl(authStore.info, 48))
const currentUserId = computed(() => authStore.info.id)
const enabled = computed(() => configStore.taskCommentsEnabled)
const actions = computed(() => {
if (!props.canWrite) {

View File

@ -3,7 +3,7 @@
<time :datetime="formatISO(task.created)" v-tooltip="formatDateLong(task.created)">
<i18n-t keypath="task.detail.created" scope="global">
<span>{{ formatDateSince(task.created) }}</span>
{{ task.createdBy.getDisplayName() }}
{{ getDisplayName(task.createdBy) }}
</i18n-t>
</time>
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
@ -30,6 +30,7 @@
import {computed, toRefs, type PropType} from 'vue'
import type {ITask} from '@/modelTypes/ITask'
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getDisplayName} from '@/models/user'
const props = defineProps({
task: {

View File

@ -38,13 +38,13 @@
</template>
<script setup lang="ts">
import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount, type PropType} from 'vue'
import {useStore} from '@/store'
import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount, toRef, type PropType} from 'vue'
import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component'
import TaskService from '@/services/task'
import type {ITask} from '@/modelTypes/ITask'
import {useAuthStore} from '@/stores/auth'
const props = defineProps({
modelValue: {
@ -55,7 +55,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue'])
const {t} = useI18n({useScope: 'global'})
const store = useStore()
const authStore = useAuthStore()
const taskService = shallowReactive(new TaskService())
const task = ref<ITask>()
@ -63,12 +63,12 @@ const task = ref<ITask>()
// We're saving the due date seperately to prevent null errors in very short periods where the task is null.
const dueDate = ref<Date>()
const lastValue = ref<Date>()
const changeInterval = ref<number>()
const changeInterval = ref<ReturnType<typeof setInterval>>()
watch(
() => props.modelValue,
toRef(props, 'modelValue'),
(value) => {
task.value = value
task.value = { ...value }
dueDate.value = value.dueDate
lastValue.value = value.dueDate
},
@ -103,7 +103,7 @@ const flatPickerConfig = computed(() => ({
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: store.state.auth.settings.weekStart,
firstDayOfWeek: authStore.settings.weekStart,
},
}))
@ -123,9 +123,10 @@ async function updateDueDate() {
return
}
// FIXME: direct prop manipulation
task.value.dueDate = new Date(dueDate.value)
const newTask = await taskService.update(task.value)
const newTask = await taskService.update({
...task.value,
dueDate: new Date(dueDate.value),
})
lastValue.value = newTask.dueDate
task.value = newTask
emit('update:modelValue', newTask)