fix(B10): add explicit not found route
This commit is contained in:
parent
36544d10d4
commit
ee7404ac61
64
web/src/components/layout/AppShell.dom.test.tsx
Normal file
64
web/src/components/layout/AppShell.dom.test.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { AppShell } from './AppShell';
|
||||||
|
|
||||||
|
vi.mock('./Sidebar', () => ({
|
||||||
|
Sidebar: () => <aside>Sidebar</aside>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./Header', () => ({
|
||||||
|
Header: () => <header>Header</header>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./RightPanel', () => ({
|
||||||
|
RightPanel: () => <aside>Right panel</aside>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/HomeView', () => ({
|
||||||
|
HomeView: () => <div>Home view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/PortfolioView', () => ({
|
||||||
|
PortfolioView: () => <div>Portfolio view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/ResearchView', () => ({
|
||||||
|
ResearchView: () => <div>Research view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/MarketsView', () => ({
|
||||||
|
MarketsView: () => <div>Markets view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/ScreenerView', () => ({
|
||||||
|
ScreenerView: () => <div>Screener view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/WatchlistView', () => ({
|
||||||
|
WatchlistView: () => <div>Watchlist view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/AlertsView', () => ({
|
||||||
|
AlertsView: () => <div>Alerts view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../views/SettingsView', () => ({
|
||||||
|
SettingsView: () => <div>Settings view</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AppShell routing fallback', () => {
|
||||||
|
it('shows a useful 404 state for unknown routes', () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={['/missing/workspace']}>
|
||||||
|
<AppShell />
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole('heading', { name: 'Route not found' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('/missing/workspace')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: 'Return home' })).toHaveAttribute('href', '/');
|
||||||
|
expect(screen.queryByText('Home view')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Routes, Route } from 'react-router-dom';
|
import { Link, Routes, Route, useLocation } from 'react-router-dom';
|
||||||
import { Sidebar } from './Sidebar';
|
import { Sidebar } from './Sidebar';
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { RightPanel } from './RightPanel';
|
import { RightPanel } from './RightPanel';
|
||||||
@ -11,6 +11,54 @@ import { WatchlistView } from '../../views/WatchlistView';
|
|||||||
import { AlertsView } from '../../views/AlertsView';
|
import { AlertsView } from '../../views/AlertsView';
|
||||||
import { SettingsView } from '../../views/SettingsView';
|
import { SettingsView } from '../../views/SettingsView';
|
||||||
|
|
||||||
|
function NotFoundView() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
aria-labelledby="not-found-title"
|
||||||
|
style={{
|
||||||
|
minHeight: 420,
|
||||||
|
borderRadius: 24,
|
||||||
|
border: '1px solid #E5E7EB',
|
||||||
|
background: 'linear-gradient(135deg, #FFF7ED 0%, #EFF6FF 100%)',
|
||||||
|
padding: '56px 32px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#111827',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 900, letterSpacing: '0.16em', color: '#2563EB', textTransform: 'uppercase' }}>
|
||||||
|
404
|
||||||
|
</div>
|
||||||
|
<h1 id="not-found-title" style={{ margin: '10px 0 8px', fontSize: 34, fontWeight: 900 }}>
|
||||||
|
Route not found
|
||||||
|
</h1>
|
||||||
|
<p style={{ margin: 0, maxWidth: 520, color: '#4B5563', fontSize: 14, lineHeight: 1.6 }}>
|
||||||
|
No trading workspace exists at <code style={{ fontWeight: 800 }}>{location.pathname}</code>. The app is still running normally.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: '#111827',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '10px 18px',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 800,
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Return home
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function AppShell() {
|
export function AppShell() {
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-shell">
|
<div className="dashboard-shell">
|
||||||
@ -35,8 +83,7 @@ export function AppShell() {
|
|||||||
<Route path="/watchlist" element={<WatchlistView />} />
|
<Route path="/watchlist" element={<WatchlistView />} />
|
||||||
<Route path="/alerts" element={<AlertsView />} />
|
<Route path="/alerts" element={<AlertsView />} />
|
||||||
<Route path="/settings" element={<SettingsView />} />
|
<Route path="/settings" element={<SettingsView />} />
|
||||||
{/* Fallback */}
|
<Route path="*" element={<NotFoundView />} />
|
||||||
<Route path="*" element={<HomeView />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user