Implements the full User Connections & Notification Centre feature: Phase 1 - Database: migrations 039-043 adding umbral_name to users, profile/social fields to settings, notifications table, connection request/user_connection tables, and linked_user_id to people. Phase 2 - Notifications: backend CRUD router + service + 90-day purge, frontend NotificationsPage with All/Unread filter, bell icon in sidebar with unread badge polling every 60s. Phase 3 - Settings: profile fields (phone, mobile, address, company, job_title), social card with accept_connections toggle and per-field sharing defaults, umbral name display with CopyableField. Phase 4 - Connections: timing-safe user search, send/accept/reject flow with atomic status updates, bidirectional UserConnection + Person records, in-app + ntfy notifications, per-receiver pending cap, nginx rate limiting. Phase 5 - People integration: batch-loaded shared profiles (N+1 prevention), Ghost icon for umbral contacts, Umbral filter pill, split Add Person button, shared field indicators (synced labels + Lock icons), disabled form inputs for synced fields on umbral contacts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
import { lazy, Suspense } from 'react';
|
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
import { useAuth } from '@/hooks/useAuth';
|
|
import LockScreen from '@/components/auth/LockScreen';
|
|
import AppLayout from '@/components/layout/AppLayout';
|
|
import DashboardPage from '@/components/dashboard/DashboardPage';
|
|
import TodosPage from '@/components/todos/TodosPage';
|
|
import CalendarPage from '@/components/calendar/CalendarPage';
|
|
import RemindersPage from '@/components/reminders/RemindersPage';
|
|
import ProjectsPage from '@/components/projects/ProjectsPage';
|
|
import ProjectDetail from '@/components/projects/ProjectDetail';
|
|
import PeoplePage from '@/components/people/PeoplePage';
|
|
import LocationsPage from '@/components/locations/LocationsPage';
|
|
import SettingsPage from '@/components/settings/SettingsPage';
|
|
import NotificationsPage from '@/components/notifications/NotificationsPage';
|
|
|
|
const AdminPortal = lazy(() => import('@/components/admin/AdminPortal'));
|
|
|
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
const { authStatus, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-screen items-center justify-center">
|
|
<div className="text-muted-foreground">Loading...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!authStatus?.authenticated) {
|
|
return <Navigate to="/login" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function AdminRoute({ children }: { children: React.ReactNode }) {
|
|
const { authStatus, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-screen items-center justify-center">
|
|
<div className="text-muted-foreground">Loading...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!authStatus?.authenticated || authStatus?.role !== 'admin') {
|
|
return <Navigate to="/dashboard" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function App() {
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<LockScreen />} />
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<ProtectedRoute>
|
|
<AppLayout />
|
|
</ProtectedRoute>
|
|
}
|
|
>
|
|
<Route index element={<Navigate to="/dashboard" replace />} />
|
|
<Route path="dashboard" element={<DashboardPage />} />
|
|
<Route path="todos" element={<TodosPage />} />
|
|
<Route path="calendar" element={<CalendarPage />} />
|
|
<Route path="reminders" element={<RemindersPage />} />
|
|
<Route path="projects" element={<ProjectsPage />} />
|
|
<Route path="projects/:id" element={<ProjectDetail />} />
|
|
<Route path="people" element={<PeoplePage />} />
|
|
<Route path="locations" element={<LocationsPage />} />
|
|
<Route path="notifications" element={<NotificationsPage />} />
|
|
<Route path="settings" element={<SettingsPage />} />
|
|
<Route
|
|
path="admin/*"
|
|
element={
|
|
<AdminRoute>
|
|
<Suspense fallback={<div className="flex h-full items-center justify-center text-muted-foreground">Loading...</div>}>
|
|
<AdminPortal />
|
|
</Suspense>
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
</Route>
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
export default App;
|