This repository has been archived on 2024-02-08. You can view files and clone it, but cannot push or open issues or pull requests.
frontend/src/router/index.ts

519 lines
14 KiB
TypeScript

import { createRouter, createWebHistory } from 'vue-router'
import type { RouteLocation } from 'vue-router'
import {saveLastVisited} from '@/helpers/saveLastVisited'
import {saveListView, getListView} from '@/helpers/saveListView'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
import {getNextWeekDate} from '@/helpers/time/getNextWeekDate'
import {setTitle} from '@/helpers/setTitle'
import {useListStore} from '@/stores/lists'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
import HomeComponent from '@/views/Home.vue'
import NotFoundComponent from '@/views/404.vue'
const About = () => import('@/views/About.vue')
// User Handling
import LoginComponent from '@/views/user/Login.vue'
import RegisterComponent from '@/views/user/Register.vue'
import OpenIdAuth from '@/views/user/OpenIdAuth.vue'
const DataExportDownload = () => import('@/views/user/DataExportDownload.vue')
// Tasks
import UpcomingTasksComponent from '@/views/tasks/ShowTasks.vue'
import LinkShareAuthComponent from '@/views/sharing/LinkSharingAuth.vue'
const ListNamespaces = () => import('@/views/namespaces/ListNamespaces.vue')
const TaskDetailView = () => import('@/views/tasks/TaskDetailView.vue')
// Team Handling
const ListTeamsComponent = () => import('@/views/teams/ListTeams.vue')
// Label Handling
const ListLabelsComponent = () => import('@/views/labels/ListLabels.vue')
const NewLabelComponent = () => import('@/views/labels/NewLabel.vue')
// Migration
const MigrationComponent = () => import('@/views/migrate/Migration.vue')
const MigrationHandlerComponent = () => import('@/views/migrate/MigrationHandler.vue')
// List Views
const ListList = () => import('@/views/list/ListList.vue')
const ListGantt = () => import('@/views/list/ListGantt.vue')
const ListTable = () => import('@/views/list/ListTable.vue')
const ListKanban = () => import('@/views/list/ListKanban.vue')
const ListInfo = () => import('@/views/list/ListInfo.vue')
// List Settings
const ListSettingEdit = () => import('@/views/list/settings/edit.vue')
const ListSettingBackground = () => import('@/views/list/settings/background.vue')
const ListSettingDuplicate = () => import('@/views/list/settings/duplicate.vue')
const ListSettingShare = () => import('@/views/list/settings/share.vue')
const ListSettingDelete = () => import('@/views/list/settings/delete.vue')
const ListSettingArchive = () => import('@/views/list/settings/archive.vue')
// Namespace Settings
const NamespaceSettingEdit = () => import('@/views/namespaces/settings/edit.vue')
const NamespaceSettingShare = () => import('@/views/namespaces/settings/share.vue')
const NamespaceSettingArchive = () => import('@/views/namespaces/settings/archive.vue')
const NamespaceSettingDelete = () => import('@/views/namespaces/settings/delete.vue')
// Saved Filters
const FilterNew = () => import('@/views/filters/FilterNew.vue')
const FilterEdit = () => import('@/views/filters/FilterEdit.vue')
const FilterDelete = () => import('@/views/filters/FilterDelete.vue')
const PasswordResetComponent = () => import('@/views/user/PasswordReset.vue')
const GetPasswordResetComponent = () => import('@/views/user/RequestPasswordReset.vue')
const UserSettingsComponent = () => import('@/views/user/Settings.vue')
const UserSettingsAvatarComponent = () => import('@/views/user/settings/Avatar.vue')
const UserSettingsCaldavComponent = () => import('@/views/user/settings/Caldav.vue')
const UserSettingsDataExportComponent = () => import('@/views/user/settings/DataExport.vue')
const UserSettingsDeletionComponent = () => import('@/views/user/settings/Deletion.vue')
const UserSettingsEmailUpdateComponent = () => import('@/views/user/settings/EmailUpdate.vue')
const UserSettingsGeneralComponent = () => import('@/views/user/settings/General.vue')
const UserSettingsPasswordUpdateComponent = () => import('@/views/user/settings/PasswordUpdate.vue')
const UserSettingsTOTPComponent = () => import('@/views/user/settings/TOTP.vue')
// List Handling
const NewListComponent = () => import('@/views/list/NewList.vue')
// Namespace Handling
const NewNamespaceComponent = () => import('@/views/namespaces/NewNamespace.vue')
const EditTeamComponent = () => import('@/views/teams/EditTeam.vue')
const NewTeamComponent = () => import('@/views/teams/NewTeam.vue')
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
scrollBehavior(to, from, savedPosition) {
// If the user is using their forward/backward keys to navigate, we want to restore the scroll view
if (savedPosition) {
return savedPosition
}
// Scroll to anchor should still work
if (to.hash) {
return {el: to.hash}
}
// Otherwise just scroll to the top
return {left: 0, top: 0}
},
routes: [
{
path: '/',
name: 'home',
component: HomeComponent,
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: NotFoundComponent,
},
// if you omit the last `*`, the `/` character in params will be encoded when resolving or pushing
{
path: '/:pathMatch(.*)',
name: 'bad-not-found',
component: NotFoundComponent,
},
{
path: '/login',
name: 'user.login',
component: LoginComponent,
meta: {
title: 'user.auth.login',
},
},
{
path: '/get-password-reset',
name: 'user.password-reset.request',
component: GetPasswordResetComponent,
meta: {
title: 'user.auth.resetPassword',
},
},
{
path: '/password-reset',
name: 'user.password-reset.reset',
component: PasswordResetComponent,
meta: {
title: 'user.auth.resetPassword',
},
},
{
path: '/register',
name: 'user.register',
component: RegisterComponent,
meta: {
title: 'user.auth.createAccount',
},
},
{
path: '/user/settings',
name: 'user.settings',
component: UserSettingsComponent,
redirect: {name: 'user.settings.general'},
children: [
{
path: '/user/settings/avatar',
name: 'user.settings.avatar',
component: UserSettingsAvatarComponent,
},
{
path: '/user/settings/caldav',
name: 'user.settings.caldav',
component: UserSettingsCaldavComponent,
},
{
path: '/user/settings/data-export',
name: 'user.settings.data-export',
component: UserSettingsDataExportComponent,
},
{
path: '/user/settings/deletion',
name: 'user.settings.deletion',
component: UserSettingsDeletionComponent,
},
{
path: '/user/settings/email-update',
name: 'user.settings.email-update',
component: UserSettingsEmailUpdateComponent,
},
{
path: '/user/settings/general',
name: 'user.settings.general',
component: UserSettingsGeneralComponent,
},
{
path: '/user/settings/password-update',
name: 'user.settings.password-update',
component: UserSettingsPasswordUpdateComponent,
},
{
path: '/user/settings/totp',
name: 'user.settings.totp',
component: UserSettingsTOTPComponent,
},
],
},
{
path: '/user/export/download',
name: 'user.export.download',
component: DataExportDownload,
},
{
path: '/share/:share/auth',
name: 'link-share.auth',
component: LinkShareAuthComponent,
},
{
path: '/namespaces',
name: 'namespaces.index',
component: ListNamespaces,
},
{
path: '/namespaces/new',
name: 'namespace.create',
component: NewNamespaceComponent,
meta: {
showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/edit',
name: 'namespace.settings.edit',
component: NamespaceSettingEdit,
meta: {
showAsModal: true,
},
props: route => ({ namespaceId: Number(route.params.id as string) }),
},
{
path: '/namespaces/:namespaceId/settings/share',
name: 'namespace.settings.share',
component: NamespaceSettingShare,
meta: {
showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/archive',
name: 'namespace.settings.archive',
component: NamespaceSettingArchive,
meta: {
showAsModal: true,
},
props: route => ({ namespaceId: parseInt(route.params.id as string) }),
},
{
path: '/namespaces/:id/settings/delete',
name: 'namespace.settings.delete',
component: NamespaceSettingDelete,
meta: {
showAsModal: true,
},
props: route => ({ namespaceId: Number(route.params.id as string) }),
},
{
path: '/tasks/:id',
name: 'task.detail',
component: TaskDetailView,
props: route => ({ taskId: Number(route.params.id as string) }),
},
{
path: '/tasks/by/upcoming',
name: 'tasks.range',
component: UpcomingTasksComponent,
props: route => ({
dateFrom: parseDateOrString(route.query.from as string, new Date()),
dateTo: parseDateOrString(route.query.to as string, getNextWeekDate()),
showNulls: route.query.showNulls === 'true',
showOverdue: route.query.showOverdue === 'true',
}),
},
{
path: '/lists/new/:namespaceId/',
name: 'list.create',
component: NewListComponent,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/edit',
name: 'list.settings.edit',
component: ListSettingEdit,
props: route => ({ listId: Number(route.params.listId as string) }),
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/background',
name: 'list.settings.background',
component: ListSettingBackground,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.settings.duplicate',
component: ListSettingDuplicate,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/share',
name: 'list.settings.share',
component: ListSettingShare,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/delete',
name: 'list.settings.delete',
component: ListSettingDelete,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/archive',
name: 'list.settings.archive',
component: ListSettingArchive,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.settings.edit',
component: FilterEdit,
meta: {
showAsModal: true,
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.settings.delete',
component: FilterDelete,
meta: {
showAsModal: true,
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
{
path: '/lists/:listId/info',
name: 'list.info',
component: ListInfo,
meta: {
showAsModal: true,
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
{
path: '/lists/:listId',
name: 'list.index',
redirect(to) {
// Redirect the user to list view by default
const savedListView = getListView(to.params.listId)
console.debug('Replaced list view with', savedListView)
return {
name: router.hasRoute(savedListView)
? savedListView
: 'list.list',
params: {listId: to.params.listId},
}
},
},
{
path: '/lists/:listId/list',
name: 'list.list',
component: ListList,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
props: route => ({ listId: Number(route.params.listId as string) }),
},
{
path: '/lists/:listId/gantt',
name: 'list.gantt',
component: ListGantt,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
// FIXME: test if `useRoute` would be the same. If it would use it instead.
props: route => ({route}),
},
{
path: '/lists/:listId/table',
name: 'list.table',
component: ListTable,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
props: route => ({ listId: Number(route.params.listId as string) }),
},
{
path: '/lists/:listId/kanban',
name: 'list.kanban',
component: ListKanban,
beforeEnter: (to) => {
saveListView(to.params.listId, to.name)
// Properly set the page title when a task popup is closed
const listStore = useListStore()
const listFromStore = listStore.getListById(Number(to.params.listId))
if(listFromStore) {
setTitle(listFromStore.title)
}
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
{
path: '/teams',
name: 'teams.index',
component: ListTeamsComponent,
},
{
path: '/teams/new',
name: 'teams.create',
component: NewTeamComponent,
meta: {
showAsModal: true,
},
},
{
path: '/teams/:id/edit',
name: 'teams.edit',
component: EditTeamComponent,
},
{
path: '/labels',
name: 'labels.index',
component: ListLabelsComponent,
},
{
path: '/labels/new',
name: 'labels.create',
component: NewLabelComponent,
meta: {
showAsModal: true,
},
},
{
path: '/migrate',
name: 'migrate.start',
component: MigrationComponent,
},
{
path: '/migrate/:service',
name: 'migrate.service',
component: MigrationHandlerComponent,
props: route => ({
service: route.params.service as string,
code: route.query.code as string,
}),
},
{
path: '/filters/new',
name: 'filters.create',
component: FilterNew,
meta: {
showAsModal: true,
},
},
{
path: '/auth/openid/:provider',
name: 'openid.auth',
component: OpenIdAuth,
},
{
path: '/about',
name: 'about',
component: About,
},
],
})
export async function getAuthForRoute(route: RouteLocation) {
const authStore = useAuthStore()
if (authStore.authUser || authStore.authLinkShare) {
return
}
const baseStore = useBaseStore()
// When trying this before the current user was fully loaded we might get a flash of the login screen
// in the user shell. To make shure this does not happen we check if everything is ready before trying.
if (!baseStore.ready) {
return
}
// Check if the user is already logged in and redirect them to the home page if not
if (
![
'user.login',
'user.password-reset.request',
'user.password-reset.reset',
'user.register',
'link-share.auth',
'openid.auth',
].includes(route.name as string) &&
localStorage.getItem('passwordResetToken') === null &&
localStorage.getItem('emailConfirmToken') === null &&
!(route.name === 'home' && (typeof route.query.userPasswordReset !== 'undefined' || typeof route.query.userEmailConfirm !== 'undefined'))
) {
saveLastVisited(route.name as string, route.params, route.query)
return {name: 'user.login'}
}
if(localStorage.getItem('passwordResetToken') !== null && route.name !== 'user.password-reset.reset') {
return {name: 'user.password-reset.reset'}
}
if(localStorage.getItem('emailConfirmToken') !== null && route.name !== 'user.login') {
return {name: 'user.login'}
}
}
router.beforeEach(async (to) => {
return getAuthForRoute(to)
})
export default router