fix: honor default project landing page
Share default landing-page resolution across startup and login redirects so default projects are loaded before navigation, while preserving explicit Overview navigation.
This commit is contained in:
parent
0aa3952e34
commit
34d67823cb
|
|
@ -1,8 +1,7 @@
|
|||
import {useRouter} from 'vue-router'
|
||||
import {getLastVisited, clearLastVisited, getLastVisitedPage} from '@/helpers/saveLastVisited'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
|
||||
import {getDefaultPageRoute} from '@/helpers/getDefaultPageRoute'
|
||||
import {getLastVisited, clearLastVisited} from '@/helpers/saveLastVisited'
|
||||
|
||||
export function useRedirectToLastVisited() {
|
||||
|
||||
|
|
@ -22,37 +21,10 @@ export function useRedirectToLastVisited() {
|
|||
}
|
||||
}
|
||||
|
||||
function getDefaultPageRoute() {
|
||||
const authStore = useAuthStore()
|
||||
const projectStore = useProjectStore()
|
||||
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 && projectStore.projects[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() {
|
||||
async function redirectIfSaved() {
|
||||
const lastRoute = getLastVisitedRoute()
|
||||
if (!lastRoute) {
|
||||
return router.push(getDefaultPageRoute())
|
||||
return router.push(await getDefaultPageRoute() ?? {name: 'home'})
|
||||
}
|
||||
|
||||
return router.push(lastRoute)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import type {RouteLocationRaw} from 'vue-router'
|
||||
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
import {getLastVisitedPage} from '@/helpers/saveLastVisited'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
export async function getDefaultPageRoute(): Promise<RouteLocationRaw | undefined> {
|
||||
const authStore = useAuthStore()
|
||||
const projectStore = useProjectStore()
|
||||
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) {
|
||||
try {
|
||||
await projectStore.loadProject(projectId)
|
||||
return {name: 'project.index', params: {projectId}}
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
case DEFAULT_PAGE.LAST_VISITED: {
|
||||
const last = getLastVisitedPage()
|
||||
if (last) {
|
||||
return {name: last.name, params: last.params, query: last.query}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import type { RouteLocation } from 'vue-router'
|
||||
import {saveLastVisited, saveLastVisitedPage, getLastVisitedPage} from '@/helpers/saveLastVisited'
|
||||
import {saveLastVisited, saveLastVisitedPage} from '@/helpers/saveLastVisited'
|
||||
import {getDefaultPageRoute} from '@/helpers/getDefaultPageRoute'
|
||||
|
||||
import {getProjectViewId} from '@/helpers/projectView'
|
||||
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||
|
|
@ -8,11 +9,9 @@ import {getNextWeekDate} from '@/helpers/time/getNextWeekDate'
|
|||
import {LINK_SHARE_HASH_PREFIX} from '@/constants/linkShareHash'
|
||||
import {REDIRECT_HASH_PREFIX} from '@/constants/redirectHash'
|
||||
import {AUTH_ROUTE_NAMES} from '@/constants/authRouteNames'
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
import {PRO_FEATURE} from '@/constants/proFeatures'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
|
||||
|
|
@ -53,39 +52,7 @@ const router = createRouter({
|
|||
return
|
||||
}
|
||||
|
||||
const redirectKey = 'defaultPageRedirectDone'
|
||||
if (sessionStorage.getItem(redirectKey)) {
|
||||
return
|
||||
}
|
||||
sessionStorage.setItem(redirectKey, 'true')
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const projectStore = useProjectStore()
|
||||
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) {
|
||||
try {
|
||||
await projectStore.loadProject(projectId)
|
||||
return {name: 'project.index', params: {projectId}}
|
||||
} catch {
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case DEFAULT_PAGE.LAST_VISITED: {
|
||||
const last = getLastVisitedPage()
|
||||
if (last) {
|
||||
return {name: last.name, params: last.params, query: last.query}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return getDefaultPageRoute()
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -578,7 +545,7 @@ export async function getAuthForRoute(to: RouteLocation, authStore) {
|
|||
const isValidUserAppRoute = !AUTH_ROUTE_NAMES.has(to.name as string) &&
|
||||
localStorage.getItem('emailConfirmToken') === null
|
||||
|
||||
if (isValidUserAppRoute) {
|
||||
if (isValidUserAppRoute && to.name !== 'home') {
|
||||
saveLastVisited(to.name as string, to.params, to.query)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import {test, expect} from '../../support/fixtures'
|
|||
import {ProjectFactory} from '../../factories/project'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {login} from '../../support/authenticateUser'
|
||||
import {login as apiLogin, setupApiUrl} from '../../support/authenticateUser'
|
||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
import {createDefaultViews} from '../project/prepareProjects'
|
||||
import {TEST_PASSWORD} from '../../support/constants'
|
||||
|
||||
test.describe('Default Landing Page', () => {
|
||||
test('shows overview page with default settings when no last visited page exists', async ({authenticatedPage: page}) => {
|
||||
|
|
@ -19,7 +20,7 @@ test.describe('Default Landing Page', () => {
|
|||
frontend_settings: JSON.stringify({defaultPage: 'upcoming'}),
|
||||
}))[0]
|
||||
await ProjectFactory.create(1, {owner_id: user.id})
|
||||
await login(page, apiContext, user)
|
||||
await apiLogin(page, apiContext, user)
|
||||
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/tasks/by/upcoming**')
|
||||
|
|
@ -32,7 +33,7 @@ test.describe('Default Landing Page', () => {
|
|||
const project = (await ProjectFactory.create(1, {owner_id: user.id}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
|
||||
const {token} = await login(page, apiContext, user)
|
||||
const {token} = await apiLogin(page, apiContext, user)
|
||||
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: project.id,
|
||||
|
|
@ -43,13 +44,41 @@ test.describe('Default Landing Page', () => {
|
|||
await page.waitForURL(`**/projects/${project.id}/**`)
|
||||
})
|
||||
|
||||
test('redirects to default project after logging in from root', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'defaultProject'}),
|
||||
}))[0]
|
||||
const project = (await ProjectFactory.create(1, {owner_id: user.id}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
|
||||
const {token} = await apiLogin(null, apiContext, user)
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: project.id,
|
||||
overdue_tasks_reminders_time: '9:00',
|
||||
})
|
||||
|
||||
await setupApiUrl(page)
|
||||
await page.goto('/')
|
||||
await expect(page).toHaveURL(/\/login/)
|
||||
|
||||
await page.locator('input[id=username]').fill(user.username)
|
||||
await page.locator('input[id=password]').fill(TEST_PASSWORD)
|
||||
await page.locator('.button').filter({hasText: 'Login'}).click()
|
||||
|
||||
await page.waitForURL(`**/projects/${project.id}/**`)
|
||||
|
||||
await page.locator('nav.menu.top-menu a').filter({hasText: 'Overview'}).click()
|
||||
await page.waitForLoadState('networkidle')
|
||||
await expect(page).toHaveURL('/')
|
||||
})
|
||||
|
||||
test('falls back to overview when default project does not exist', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'defaultProject'}),
|
||||
}))[0]
|
||||
await ProjectFactory.create(1, {owner_id: user.id})
|
||||
|
||||
const {token} = await login(page, apiContext, user)
|
||||
const {token} = await apiLogin(page, apiContext, user)
|
||||
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: 999999,
|
||||
|
|
@ -69,7 +98,7 @@ test.describe('Default Landing Page', () => {
|
|||
const views = await createDefaultViews(project.id)
|
||||
await TaskFactory.create(1, {project_id: project.id})
|
||||
|
||||
await login(page, apiContext, user)
|
||||
await apiLogin(page, apiContext, user)
|
||||
|
||||
await page.goto(`/projects/${project.id}/${views[0].id}`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
|
@ -86,7 +115,7 @@ test.describe('Default Landing Page', () => {
|
|||
await createDefaultViews(project.id)
|
||||
await TaskFactory.create(1, {project_id: project.id})
|
||||
|
||||
await login(page, apiContext, user)
|
||||
await apiLogin(page, apiContext, user)
|
||||
|
||||
await page.goto(`/projects/${project.id}/1`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ import {ProjectViewFactory} from '../../factories/project_view'
|
|||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
import type {Page} from '@playwright/test'
|
||||
|
||||
async function visitProjectsToBuildHistory(page: Page, projects: any[]) {
|
||||
interface ProjectHistoryEntry {
|
||||
id: number
|
||||
}
|
||||
|
||||
interface ProjectSeed {
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
async function visitProjectsToBuildHistory(page: Page, projects: ProjectSeed[]) {
|
||||
for (const project of projects) {
|
||||
const loadProjectPromise = page.waitForResponse(response =>
|
||||
response.url().includes(`/projects/${project.id}`) && response.request().method() === 'GET',
|
||||
|
|
@ -14,7 +23,7 @@ async function visitProjectsToBuildHistory(page: Page, projects: any[]) {
|
|||
await page.waitForFunction(
|
||||
(projectId) => {
|
||||
const history = JSON.parse(localStorage.getItem('projectHistory') || '[]')
|
||||
return history.some((h: any) => h.id === projectId)
|
||||
return history.some((h: ProjectHistoryEntry) => h.id === projectId)
|
||||
},
|
||||
project.id,
|
||||
)
|
||||
|
|
@ -47,7 +56,7 @@ test.describe('Project History', () => {
|
|||
await page.waitForFunction(
|
||||
(projectId) => {
|
||||
const history = JSON.parse(localStorage.getItem('projectHistory') || '[]')
|
||||
return history.some((h: any) => h.id === projectId)
|
||||
return history.some((h: ProjectHistoryEntry) => h.id === projectId)
|
||||
},
|
||||
projects[i].id,
|
||||
)
|
||||
|
|
@ -76,11 +85,16 @@ test.describe('Project History', () => {
|
|||
}, false)
|
||||
}
|
||||
|
||||
// Navigate to home first so the default-page redirect guard is primed
|
||||
// (sets sessionStorage flag), preventing later page.goto('/') from
|
||||
// redirecting to the last visited project.
|
||||
// Keep reloads on the overview page while this test focuses on the
|
||||
// last-viewed section visibility.
|
||||
await page.goto('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'))
|
||||
await updateUserSettings(apiContext, token!, {
|
||||
frontendSettings: {
|
||||
defaultPage: 'overview',
|
||||
},
|
||||
})
|
||||
|
||||
// Visit projects to build up history
|
||||
await visitProjectsToBuildHistory(page, projects)
|
||||
|
|
@ -90,7 +104,6 @@ test.describe('Project History', () => {
|
|||
await expect(page.locator('body')).toContainText('Last viewed')
|
||||
|
||||
// Disable the setting via API
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'))
|
||||
await updateUserSettings(apiContext, token!, {
|
||||
frontendSettings: {
|
||||
showLastViewed: false,
|
||||
|
|
@ -122,6 +135,7 @@ test.describe('Project History', () => {
|
|||
const token = await page.evaluate(() => localStorage.getItem('token'))
|
||||
await updateUserSettings(apiContext, token!, {
|
||||
frontendSettings: {
|
||||
defaultPage: 'overview',
|
||||
showLastViewed: false,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue