5d53f35dea
- layoutStore: remove 4 dead field groups (pinnedPanels, selectedResultId, compareTrayVisible, notificationsOpen) — none were read outside the store - CompareTray: drop dead useLayoutStore side-effect (state was write-only) - 8 components: replace bare useStore() with explicit selectors / useShallow to prevent unnecessary re-renders on unrelated state mutations - lib/constants: add STALE_MARKET_SIGNALS + STALE_REVIEW_QUEUE (30s each) - useMarketSignals, useReviewQueue: use global constants instead of hook-local magic numbers - Add STATE_MANAGEMENT.md: decision tree + rules for RQ/Zustand/local/derived Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
94 lines
4.3 KiB
TypeScript
94 lines
4.3 KiB
TypeScript
import { lazy, Suspense } from 'react'
|
|
import { Routes, Route, Navigate } from 'react-router'
|
|
import { LoadingPage, AppErrorBoundary } from './components/ui'
|
|
import { AppShell } from './components/layout'
|
|
import { ProtectedRoute } from './components/auth'
|
|
import { WorkspaceType } from './domain/enums'
|
|
import { useSessionStore } from './stores/sessionStore'
|
|
|
|
const WORKSPACE_HOME: Record<string, string> = {
|
|
[WorkspaceType.SUPPLY]: '/supply/dashboard',
|
|
[WorkspaceType.DEMAND]: '/demand/ai-search',
|
|
}
|
|
|
|
function RoleRedirect() {
|
|
const currentUser = useSessionStore(s => s.currentUser)
|
|
const first = currentUser?.allowedWorkspaces[0] ?? WorkspaceType.SUPPLY
|
|
return <Navigate to={WORKSPACE_HOME[first] ?? '/supply/dashboard'} replace />
|
|
}
|
|
|
|
const LoginScreen = lazy(() => import('./pages/auth/LoginScreen'))
|
|
|
|
const SupplyDashboard = lazy(() => import('./pages/supply/SupplyDashboard'))
|
|
const Properties = lazy(() => import('./pages/supply/Properties'))
|
|
const MatchCenter = lazy(() => import('./pages/supply/MatchCenter'))
|
|
const Anfragencenter = lazy(() => import('./pages/supply/Anfragencenter'))
|
|
const FutureAvailability = lazy(() => import('./pages/supply/FutureAvailability'))
|
|
const DataQuality = lazy(() => import('./pages/supply/DataQuality'))
|
|
const ReminderManager = lazy(() => import('./pages/supply/ReminderManager'))
|
|
const MarketLeads = lazy(() => import('./pages/supply/MarketLeads'))
|
|
const NewListing = lazy(() => import('./pages/supply/NewListing'))
|
|
const MyListings = lazy(() => import('./pages/supply/MyListings'))
|
|
|
|
const AISearch = lazy(() => import('./pages/demand/AISearch'))
|
|
const Results = lazy(() => import('./pages/demand/Results'))
|
|
const MatchDetail = lazy(() => import('./pages/demand/MatchDetail'))
|
|
const Compare = lazy(() => import('./pages/demand/Compare'))
|
|
const Anfragen = lazy(() => import('./pages/demand/Anfragen'))
|
|
const Pipeline = lazy(() => import('./pages/demand/Pipeline'))
|
|
const PropertyDetail = lazy(() => import('./pages/demand/PropertyDetail'))
|
|
|
|
const MarketIntelligence = lazy(() => import('./pages/ops/MarketIntelligence'))
|
|
|
|
function App() {
|
|
return (
|
|
<AppErrorBoundary>
|
|
<Suspense fallback={<LoadingPage />}>
|
|
<Routes>
|
|
{/* Public */}
|
|
<Route path="/auth/login" element={<LoginScreen />} />
|
|
|
|
{/* Protected: auth check only */}
|
|
<Route element={<ProtectedRoute />}>
|
|
<Route element={<AppShell />}>
|
|
<Route path="/" element={<RoleRedirect />} />
|
|
|
|
{/* Supply Workspace */}
|
|
<Route element={<ProtectedRoute workspace={WorkspaceType.SUPPLY} />}>
|
|
<Route path="/supply/dashboard" element={<SupplyDashboard />} />
|
|
<Route path="/supply/properties" element={<Properties />} />
|
|
<Route path="/supply/match-center" element={<MatchCenter />} />
|
|
<Route path="/supply/anfragen" element={<Anfragencenter />} />
|
|
<Route path="/supply/future-availability" element={<FutureAvailability />} />
|
|
<Route path="/supply/data-quality" element={<DataQuality />} />
|
|
<Route path="/supply/reminder-manager" element={<ReminderManager />} />
|
|
<Route path="/supply/market-leads" element={<MarketLeads />} />
|
|
<Route path="/supply/market-intelligence" element={<MarketIntelligence />} />
|
|
<Route path="/supply/new-listing" element={<NewListing />} />
|
|
<Route path="/supply/my-listings" element={<MyListings />} />
|
|
</Route>
|
|
|
|
{/* Demand Workspace */}
|
|
<Route element={<ProtectedRoute workspace={WorkspaceType.DEMAND} />}>
|
|
<Route path="/demand/ai-search" element={<AISearch />} />
|
|
<Route path="/demand/results" element={<Results />} />
|
|
<Route path="/demand/results/:matchId" element={<MatchDetail />} />
|
|
<Route path="/demand/compare" element={<Compare />} />
|
|
<Route path="/demand/anfragen" element={<Anfragen />} />
|
|
<Route path="/demand/pipeline" element={<Pipeline />} />
|
|
<Route path="/demand/property/:propertyId" element={<PropertyDetail />} />
|
|
</Route>
|
|
|
|
|
|
</Route>
|
|
</Route>
|
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</Suspense>
|
|
</AppErrorBoundary>
|
|
)
|
|
}
|
|
|
|
export default App
|