fix: route logo to configured landing page

Use the default landing-page resolver for logo navigation while keeping the Overview menu as an explicit overview link.
This commit is contained in:
surfingbytes 2026-06-25 13:40:30 +00:00
parent 34d67823cb
commit 150940291c
4 changed files with 37 additions and 13 deletions

View File

@ -4,16 +4,17 @@
aria-label="main navigation"
class="navbar d-print-none"
>
<RouterLink
:to="{ name: 'home' }"
<a
href="/"
class="logo-link"
:aria-label="$t('navigation.home')"
@click.prevent="openLandingPage"
>
<Logo
width="164"
height="48"
/>
</RouterLink>
</a>
<MenuButton class="menu-button" />
@ -129,7 +130,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { PERMISSIONS as Permissions } from '@/constants/permissions'
@ -147,6 +148,7 @@ import OpenQuickActions from '@/components/misc/OpenQuickActions.vue'
import { getProjectTitle } from '@/helpers/getProjectTitle'
import { isEditorContentEmpty } from '@/helpers/editorContentEmpty'
import { getDefaultPageRoute } from '@/helpers/getDefaultPageRoute'
import { useBaseStore } from '@/stores/base'
import { useConfigStore } from '@/stores/config'
@ -165,6 +167,7 @@ const menuActive = computed(() => baseStore.menuActive)
// Standalone pages (no project) surface their route's title in the header.
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const pageTitle = computed(() => {
const title = route.meta.title as string | undefined
@ -177,6 +180,10 @@ const configStore = useConfigStore()
const imprintUrl = computed(() => configStore.legal.imprintUrl)
const privacyPolicyUrl = computed(() => configStore.legal.privacyPolicyUrl)
const adminPanelEnabled = computed(() => configStore.isProFeatureEnabled(PRO_FEATURE.ADMIN_PANEL))
async function openLandingPage() {
await router.push(await getDefaultPageRoute() ?? {name: 'home'})
}
</script>
<style lang="scss" scoped>

View File

@ -5,16 +5,17 @@
:style="{'--sidebar-width': sidebarWidth}"
>
<nav class="menu top-menu">
<RouterLink
:to="{name: 'home'}"
<a
href="/"
class="logo"
:aria-label="$t('navigation.home')"
@click.prevent="openLandingPage"
>
<Logo
width="164"
height="48"
/>
</RouterLink>
</a>
<menu class="menu-list other-menu-items">
<li>
<RouterLink
@ -134,6 +135,7 @@
<script setup lang="ts">
import {computed} from 'vue'
import {useRouter} from 'vue-router'
import PoweredByLink from '@/components/home/PoweredByLink.vue'
import Logo from '@/components/home/Logo.vue'
@ -146,10 +148,12 @@ import {PRO_FEATURE} from '@/constants/proFeatures'
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
import type {IProject} from '@/modelTypes/IProject'
import {useSidebarResize} from '@/composables/useSidebarResize'
import {getDefaultPageRoute} from '@/helpers/getDefaultPageRoute'
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const configStore = useConfigStore()
const router = useRouter()
const timeTrackingEnabled = computed(() => configStore.isProFeatureEnabled(PRO_FEATURE.TIME_TRACKING))
@ -159,6 +163,10 @@ const {sidebarWidth, isResizing, startResize, isMobile} = useSidebarResize()
const projects = computed(() => projectStore.notArchivedRootProjects as IProject[])
const favoriteProjects = computed(() => projectStore.favoriteProjects as IProject[])
const savedFilterProjects = computed(() => projectStore.savedFilterProjects as IProject[])
async function openLandingPage() {
await router.push(await getDefaultPageRoute() ?? {name: 'home'})
}
</script>
<style lang="scss" scoped>

View File

@ -70,6 +70,9 @@ test.describe('Default Landing Page', () => {
await page.locator('nav.menu.top-menu a').filter({hasText: 'Overview'}).click()
await page.waitForLoadState('networkidle')
await expect(page).toHaveURL('/')
await page.locator('.logo-link').click()
await page.waitForURL(`**/projects/${project.id}/**`)
})
test('falls back to overview when default project does not exist', async ({page, apiContext}) => {
@ -107,7 +110,7 @@ test.describe('Default Landing Page', () => {
await page.waitForURL(`**/projects/${project.id}/${views[0].id}`)
})
test('does not redirect on in-app navigation to home', async ({page, apiContext}) => {
test('redirects to default page when clicking the logo', async ({page, apiContext}) => {
const user = (await UserFactory.create(1, {
frontend_settings: JSON.stringify({defaultPage: 'upcoming'}),
}))[0]
@ -121,7 +124,6 @@ test.describe('Default Landing Page', () => {
await page.waitForLoadState('networkidle')
await page.locator('.logo-link').click()
await page.waitForLoadState('networkidle')
await expect(page).toHaveURL('/')
await page.waitForURL('**/tasks/by/upcoming**')
})
})

View File

@ -3,14 +3,21 @@ import {TaskFactory} from '../../factories/task'
import {ProjectFactory} from '../../factories/project'
import {createProjects} from './prepareProjects'
interface ProjectWithViews {
id: number
title: string
views: Array<{id: number}>
}
test.describe('Projects', () => {
test.use({
// Use authenticated page for all tests
})
let projects: any[]
let projects: ProjectWithViews[]
test.beforeEach(async ({authenticatedPage}) => {
test.beforeEach(async ({authenticatedPage: page}) => {
void page
projects = await createProjects()
})
@ -72,7 +79,7 @@ test.describe('Projects', () => {
await expect(page.locator('.project-title')).toContainText(newProjectName)
await expect(page.locator('.project-title')).not.toContainText(projects[0].title)
await expect(page.locator('.menu-container .menu-list').getByRole('listitem').filter({hasText: newProjectName})).toBeVisible()
await page.locator('.logo-link').click()
await page.locator('nav.menu.top-menu a').filter({hasText: 'Overview'}).click()
await page.waitForLoadState('networkidle')
await expect(page.locator('.project-grid')).toContainText(newProjectName)
await expect(page.locator('.project-grid')).not.toContainText(projects[0].title)