Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/main/frontend/app/router.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { createBrowserRouter } from 'react-router'
import { createBrowserRouter, isRouteErrorResponse, useRouteError } from 'react-router'
import ConfigurationOverview from '~/routes/configurations/configuration-overview'
import CodeEditor from '~/routes/editor/editor'
import Help from '~/routes/help/help'
import NotFound from '~/routes/notfound/not-found'
import Settings from '~/routes/settings/settings'
import Studio from '~/routes/studio/studio'
import ProjectLanding from './routes/projectlanding/project-landing'
import AppLayout from './routes/app-layout'

function RootErrorBoundary() {
const error = useRouteError()

if (isRouteErrorResponse(error) && error.status === 404) {
return <NotFound />
}

return (
<main className="container mx-auto p-4 pt-16">
<h1>Oops!</h1>
Expand Down Expand Up @@ -49,7 +56,7 @@
element: <Settings />,
},
],
},
}

Check warning on line 59 in src/main/frontend/app/router.tsx

View workflow job for this annotation

GitHub Actions / Build & Run All Tests

Insert `,`
],
},
])
65 changes: 65 additions & 0 deletions src/main/frontend/app/routes/notfound/not-found.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const { navigateMock } = vi.hoisted(() => ({ navigateMock: vi.fn() }))

vi.mock('react-router', () => ({ useNavigate: () => navigateMock }))
vi.mock('/icons/custom/ff!-icon.svg?react', () => ({ default: () => null }))

import NotFound from './not-found'

const STORAGE_ROOT_PATH_KEY = 'active-project-root-path'

beforeEach(() => {
navigateMock.mockClear()
localStorage.clear()
})

afterEach(() => {
cleanup()
})

describe('NotFound', () => {
it('renders the not-found message', () => {
render(<NotFound />)

expect(screen.getByText('404')).toBeInTheDocument()
expect(screen.getByRole('heading', { name: 'Page not found' })).toBeInTheDocument()
})

describe('when no project has been opened', () => {
it('offers a way back to the project landing', () => {
render(<NotFound />)

expect(screen.getByRole('button', { name: 'Back to projects' })).toBeInTheDocument()
})

it('navigates to the project landing on click', () => {
render(<NotFound />)

fireEvent.click(screen.getByRole('button', { name: 'Back to projects' }))

expect(navigateMock).toHaveBeenCalledWith('/')
})
})

describe('when a project has been opened', () => {
beforeEach(() => {
localStorage.setItem(STORAGE_ROOT_PATH_KEY, '/some/project/path')
})

it('offers a way back to the configuration overview', () => {
render(<NotFound />)

expect(screen.getByRole('button', { name: 'Back to overview' })).toBeInTheDocument()
})

it('navigates to the configuration overview on click', () => {
render(<NotFound />)

fireEvent.click(screen.getByRole('button', { name: 'Back to overview' }))

expect(navigateMock).toHaveBeenCalledWith('/configurations')
})
})
})
28 changes: 28 additions & 0 deletions src/main/frontend/app/routes/notfound/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useNavigate } from 'react-router'
import FfIcon from '/icons/custom/ff!-icon.svg?react'
import Button from '~/components/inputs/button'
import { getStoredProjectRootPath } from '~/stores/project-store'

export default function NotFound() {
const navigate = useNavigate()

const hasProject = Boolean(getStoredProjectRootPath())
const destination = hasProject ? '/configurations' : '/'
const buttonLabel = hasProject ? 'Back to overview' : 'Back to projects'

return (
<div className="bg-backdrop flex min-h-screen w-full flex-col items-center justify-center gap-6 p-4 text-center">
<FfIcon className="h-16 w-auto opacity-80" />

<div className="flex flex-col items-center gap-2">
<p className="text-6xl font-bold">404</p>
<h1 className="text-foreground text-2xl font-semibold">Page not found</h1>
<p className="text-foreground-muted max-w-md">
The page you&apos;re looking for doesn&apos;t exist or may have been moved.
</p>
</div>

<Button onClick={() => navigate(destination)}>{buttonLabel}</Button>
</div>
)
}
Loading