Merge branch 'main' into feat/l1-workspace
# Conflicts: # frontend/src/router.tsx
This commit is contained in:
@@ -58,7 +58,7 @@ export function AppLayout() {
|
||||
}
|
||||
|
||||
const mobileNavItems = [
|
||||
{ path: '/', label: 'Dashboard', icon: LayoutGrid },
|
||||
{ path: '/home', label: 'Dashboard', icon: LayoutGrid },
|
||||
{ path: '/sessions', label: 'Session History', icon: Clock },
|
||||
{ path: '/escalations', label: 'Escalations', icon: AlertTriangle },
|
||||
{ path: '/trees', label: 'Guided Flows', icon: GitBranch },
|
||||
@@ -106,7 +106,7 @@ export function AppLayout() {
|
||||
style={{ background: 'var(--color-bg-sidebar)', borderRight: '1px solid var(--color-border-default)' }}
|
||||
>
|
||||
<div className="flex h-14 items-center justify-between px-4" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
|
||||
<Link to="/" className="flex items-center gap-2.5">
|
||||
<Link to="/home" className="flex items-center gap-2.5">
|
||||
<BrandLogo size="sm" />
|
||||
<span className="text-sm font-heading font-bold text-text-heading">ResolutionFlow</span>
|
||||
</Link>
|
||||
|
||||
@@ -40,7 +40,7 @@ interface Group {
|
||||
}
|
||||
|
||||
const PAGES: PaletteItem[] = [
|
||||
{ id: 'page-dashboard', group: 'pages', title: 'Dashboard', path: '/', icon: 'page' },
|
||||
{ id: 'page-dashboard', group: 'pages', title: 'Dashboard', path: '/home', icon: 'page' },
|
||||
{ id: 'page-flows', group: 'pages', title: 'All Flows', subtitle: 'Browse your flow library', path: '/trees', icon: 'page' },
|
||||
{ id: 'page-sessions', group: 'pages', title: 'Sessions', subtitle: 'View session history', path: '/sessions', icon: 'page' },
|
||||
{ id: 'page-flowpilot', group: 'pages', title: 'FlowPilot', subtitle: 'AI troubleshooting', path: '/pilot', icon: 'page' },
|
||||
|
||||
@@ -22,7 +22,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/landing" state={{ from: location }} replace />
|
||||
return <Navigate to="/" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
// Enforce must_change_password — redirect unless already on /change-password
|
||||
|
||||
@@ -63,7 +63,7 @@ export function TopBar() {
|
||||
>
|
||||
{/* Logo area */}
|
||||
<Link
|
||||
to="/"
|
||||
to="/home"
|
||||
className="flex items-center gap-2.5 pr-4 transition-all duration-200"
|
||||
>
|
||||
<BrandLogo size="sm" />
|
||||
|
||||
@@ -71,11 +71,11 @@ const FROZEN_NOW = new Date('2026-05-06T00:00:00Z')
|
||||
|
||||
function renderAppLayout() {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
<MemoryRouter initialEntries={['/home']}>
|
||||
<Routes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route
|
||||
index
|
||||
path="/home"
|
||||
element={<div data-testid="child-route-content">child route</div>}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'
|
||||
|
||||
import { ProtectedRoute } from '../ProtectedRoute'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
|
||||
/**
|
||||
* Probe component: surfaces the current pathname and `location.state.from` so
|
||||
* the test can assert both the redirect target and that the original
|
||||
* destination is preserved for post-login return.
|
||||
*/
|
||||
function LocationProbe() {
|
||||
const loc = useLocation()
|
||||
const from =
|
||||
(loc.state as { from?: { pathname?: string } } | null)?.from?.pathname ?? ''
|
||||
return (
|
||||
<>
|
||||
<div data-testid="probe-pathname">{loc.pathname}</div>
|
||||
<div data-testid="probe-from">{from}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
describe('ProtectedRoute — unauthenticated redirect', () => {
|
||||
beforeEach(() => {
|
||||
useAuthStore.setState({
|
||||
user: null,
|
||||
token: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('redirects unauthenticated visits to /home → / and preserves origin in state.from', () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/home']}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/home"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<div data-testid="home-content">home</div>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/" element={<LocationProbe />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
|
||||
// The protected page should not render.
|
||||
expect(screen.queryByTestId('home-content')).not.toBeInTheDocument()
|
||||
|
||||
// We landed on / (the public landing route), not /landing.
|
||||
expect(screen.getByTestId('probe-pathname')).toHaveTextContent('/')
|
||||
expect(screen.getByTestId('probe-from')).toHaveTextContent('/home')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user