From d9c498822950117b438bf6a598d16ef0a1353b7f Mon Sep 17 00:00:00 2001 From: surfingbytes Date: Fri, 20 Mar 2026 12:59:40 +0000 Subject: [PATCH 01/10] Add option to select landing page --- .../composables/useRedirectToLastVisited.ts | 32 +++++++++++++++-- frontend/src/constants/defaultPage.ts | 8 +++++ frontend/src/helpers/saveLastVisited.ts | 18 ++++++++++ frontend/src/i18n/lang/en.json | 7 ++++ frontend/src/modelTypes/IUserSettings.ts | 2 ++ frontend/src/models/userSettings.ts | 2 ++ frontend/src/router/index.ts | 36 ++++++++++++++++++- frontend/src/stores/auth.ts | 2 ++ frontend/src/views/user/settings/General.vue | 27 ++++++++++++++ 9 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 frontend/src/constants/defaultPage.ts diff --git a/frontend/src/composables/useRedirectToLastVisited.ts b/frontend/src/composables/useRedirectToLastVisited.ts index 87ae7854d..799488a40 100644 --- a/frontend/src/composables/useRedirectToLastVisited.ts +++ b/frontend/src/composables/useRedirectToLastVisited.ts @@ -1,5 +1,7 @@ import {useRouter} from 'vue-router' -import {getLastVisited, clearLastVisited} from '@/helpers/saveLastVisited' +import {getLastVisited, clearLastVisited, getLastVisitedPage} from '@/helpers/saveLastVisited' +import {useAuthStore} from '@/stores/auth' +import {DEFAULT_PAGE} from '@/constants/defaultPage' export function useRedirectToLastVisited() { @@ -19,10 +21,36 @@ export function useRedirectToLastVisited() { } } + function getDefaultPageRoute() { + const authStore = useAuthStore() + const defaultPage = authStore.settings?.frontendSettings?.defaultPage + + switch (defaultPage) { + case DEFAULT_PAGE.UPCOMING: + return {name: 'tasks.range'} + case DEFAULT_PAGE.DEFAULT_PROJECT: { + const projectId = authStore.settings?.defaultProjectId + if (projectId) { + return {name: 'project.index', params: {projectId}} + } + return {name: 'home'} + } + case DEFAULT_PAGE.LAST_VISITED: { + const last = getLastVisitedPage() + if (last) { + return {name: last.name, params: last.params, query: last.query} + } + return {name: 'home'} + } + default: + return {name: 'home'} + } + } + function redirectIfSaved() { const lastRoute = getLastVisitedRoute() if (!lastRoute) { - return router.push({name: 'home'}) + return router.push(getDefaultPageRoute()) } return router.push(lastRoute) diff --git a/frontend/src/constants/defaultPage.ts b/frontend/src/constants/defaultPage.ts new file mode 100644 index 000000000..a6e49514c --- /dev/null +++ b/frontend/src/constants/defaultPage.ts @@ -0,0 +1,8 @@ +export const DEFAULT_PAGE = { + OVERVIEW: 'overview', + UPCOMING: 'upcoming', + DEFAULT_PROJECT: 'defaultProject', + LAST_VISITED: 'lastVisited', +} as const + +export type DefaultPage = typeof DEFAULT_PAGE[keyof typeof DEFAULT_PAGE] diff --git a/frontend/src/helpers/saveLastVisited.ts b/frontend/src/helpers/saveLastVisited.ts index 98a32174f..5491d4d61 100644 --- a/frontend/src/helpers/saveLastVisited.ts +++ b/frontend/src/helpers/saveLastVisited.ts @@ -1,4 +1,5 @@ const LAST_VISITED_KEY = 'lastVisited' +const LAST_VISITED_PAGE_KEY = 'lastVisitedPage' export const saveLastVisited = (name: string | undefined, params: object, query: object) => { if (typeof name === 'undefined') { @@ -20,3 +21,20 @@ export const getLastVisited = () => { export const clearLastVisited = () => { return localStorage.removeItem(LAST_VISITED_KEY) } + +export const saveLastVisitedPage = (name: string | undefined, params: object, query: object) => { + if (typeof name === 'undefined') { + return + } + + localStorage.setItem(LAST_VISITED_PAGE_KEY, JSON.stringify({name, params, query})) +} + +export const getLastVisitedPage = () => { + const lastVisited = localStorage.getItem(LAST_VISITED_PAGE_KEY) + if (lastVisited === null) { + return null + } + + return JSON.parse(lastVisited) +} diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 3d4b43c12..f1e09e803 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -121,6 +121,13 @@ "12h": "12-hour (AM/PM)", "24h": "24-hour (HH:mm)" }, + "defaultPage": "Default page", + "defaultPageOptions": { + "overview": "Overview", + "upcoming": "Upcoming", + "defaultProject": "Default project", + "lastVisited": "Last visited" + }, "externalUserNameChange": "Your name is managed by your login provider ({provider}). To change it, please update it there instead." }, "sections": { diff --git a/frontend/src/modelTypes/IUserSettings.ts b/frontend/src/modelTypes/IUserSettings.ts index 246321d74..0c5c9e42c 100644 --- a/frontend/src/modelTypes/IUserSettings.ts +++ b/frontend/src/modelTypes/IUserSettings.ts @@ -8,6 +8,7 @@ import type {Priority} from '@/constants/priorities' import type {DateDisplay} from '@/constants/dateDisplay' import type {TimeFormat} from '@/constants/timeFormat' import type {IRelationKind} from '@/types/IRelationKind' +import type {DefaultPage} from '@/constants/defaultPage' export interface IFrontendSettings { playSoundWhenDone: boolean @@ -24,6 +25,7 @@ export interface IFrontendSettings { alwaysShowBucketTaskCount: boolean sidebarWidth: number | null commentSortOrder: 'asc' | 'desc' + defaultPage: DefaultPage } export interface IExtraSettingsLink { diff --git a/frontend/src/models/userSettings.ts b/frontend/src/models/userSettings.ts index a4fcc91fb..0b0412cbb 100644 --- a/frontend/src/models/userSettings.ts +++ b/frontend/src/models/userSettings.ts @@ -8,6 +8,7 @@ import {PRIORITIES} from '@/constants/priorities' import {DATE_DISPLAY} from '@/constants/dateDisplay' import {TIME_FORMAT} from '@/constants/timeFormat' import {RELATION_KIND} from '@/types/IRelationKind' +import {DEFAULT_PAGE} from '@/constants/defaultPage' export default class UserSettingsModel extends AbstractModel implements IUserSettings { name = '' @@ -35,6 +36,7 @@ export default class UserSettingsModel extends AbstractModel impl alwaysShowBucketTaskCount: false, sidebarWidth: null, commentSortOrder: 'asc', + defaultPage: DEFAULT_PAGE.LAST_VISITED, } extraSettingsLinks = {} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index c19b4642d..ee3d8ed5a 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,12 +1,13 @@ import { createRouter, createWebHistory } from 'vue-router' import type { RouteLocation } from 'vue-router' -import {saveLastVisited} from '@/helpers/saveLastVisited' +import {saveLastVisited, saveLastVisitedPage, getLastVisitedPage} from '@/helpers/saveLastVisited' import {getProjectViewId} from '@/helpers/projectView' import {parseDateOrString} from '@/helpers/time/parseDateOrString' import {getNextWeekDate} from '@/helpers/time/getNextWeekDate' import {LINK_SHARE_HASH_PREFIX} from '@/constants/linkShareHash' import {AUTH_ROUTE_NAMES} from '@/constants/authRouteNames' +import {DEFAULT_PAGE} from '@/constants/defaultPage' import {useAuthStore} from '@/stores/auth' @@ -42,6 +43,33 @@ const router = createRouter({ path: '/', name: 'home', component: () => import('@/views/Home.vue'), + beforeEnter(_to, from) { + if (from.name !== undefined) { + return + } + + const authStore = useAuthStore() + const defaultPage = authStore.settings?.frontendSettings?.defaultPage + + switch (defaultPage) { + case DEFAULT_PAGE.UPCOMING: + return {name: 'tasks.range'} + case DEFAULT_PAGE.DEFAULT_PROJECT: { + const projectId = authStore.settings?.defaultProjectId + if (projectId) { + return {name: 'project.index', params: {projectId}} + } + break + } + case DEFAULT_PAGE.LAST_VISITED: { + const last = getLastVisitedPage() + if (last) { + return {name: last.name, params: last.params, query: last.query} + } + break + } + } + }, }, { path: '/:pathMatch(.*)*', @@ -480,4 +508,10 @@ router.beforeEach(async (to, from) => { } }) +router.afterEach((to) => { + if (!AUTH_ROUTE_NAMES.has(to.name as string) && to.name !== 'home') { + saveLastVisitedPage(to.name as string, to.params, to.query) + } +}) + export default router diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index aec224245..9aaec2cba 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -25,6 +25,7 @@ import {PrefixMode} from '@/modules/parseTaskText' import {DATE_DISPLAY} from '@/constants/dateDisplay' import {TIME_FORMAT} from '@/constants/timeFormat' import {RELATION_KIND} from '@/types/IRelationKind' +import {DEFAULT_PAGE} from '@/constants/defaultPage' import type {IProvider} from '@/types/IProvider' function redirectToSpecifiedProvider() { @@ -141,6 +142,7 @@ export const useAuthStore = defineStore('auth', () => { backgroundBrightness: 100, sidebarWidth: null, commentSortOrder: 'asc', + defaultPage: DEFAULT_PAGE.LAST_VISITED, ...newSettings.frontendSettings, }, }) diff --git a/frontend/src/views/user/settings/General.vue b/frontend/src/views/user/settings/General.vue index 9643f1de8..f47996893 100644 --- a/frontend/src/views/user/settings/General.vue +++ b/frontend/src/views/user/settings/General.vue @@ -47,6 +47,24 @@ :loading="loading" >
+
+ +