feat: audit log page, meeting prep modal, communication style, error boundaries + toast
Some checks failed
CI/CD / test (push) Failing after 21s
CI/CD / deploy (push) Has been skipped

- AuditLogPage: filterable table with expandable details (admin only)
- MeetingPrepModal: AI-generated meeting briefs with health score, talking points, conversation starters
- Communication Style section in Settings: tone, greeting, signoff, writing samples, avoid words
- ErrorBoundary wrapping all page routes with Try Again button
- Global toast system with API error interceptor (401/403/500)
- ToastContainer with success/error/warning/info variants
- Print CSS for meeting prep
- Audit Log added to sidebar nav for admins
- All 80 frontend tests pass, clean build
This commit is contained in:
2026-01-30 01:21:26 +00:00
parent 22bf4778fd
commit 1340893144
11 changed files with 1019 additions and 22 deletions

View File

@@ -3,6 +3,9 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useAuthStore } from '@/stores/auth';
import Layout from '@/components/Layout';
import { PageLoader } from '@/components/LoadingSpinner';
import ErrorBoundary from '@/components/ErrorBoundary';
import { ToastContainer, toast } from '@/components/Toast';
import { api } from '@/lib/api';
const LoginPage = lazy(() => import('@/pages/LoginPage'));
const DashboardPage = lazy(() => import('@/pages/DashboardPage'));
@@ -19,6 +22,7 @@ const SegmentsPage = lazy(() => import('@/pages/SegmentsPage'));
const InvitePage = lazy(() => import('@/pages/InvitePage'));
const ForgotPasswordPage = lazy(() => import('@/pages/ForgotPasswordPage'));
const ResetPasswordPage = lazy(() => import('@/pages/ResetPasswordPage'));
const AuditLogPage = lazy(() => import('@/pages/AuditLogPage'));
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuthStore();
@@ -27,6 +31,21 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
// Setup global API error interceptor
api.setErrorHandler((status, message) => {
if (status === 401) {
toast.error('Session expired. Please log in again.');
} else if (status === 403) {
toast.error('Access denied: ' + message);
} else if (status >= 500) {
toast.error('Server error: ' + message);
}
});
function PageErrorBoundary({ children }: { children: React.ReactNode }) {
return <ErrorBoundary>{children}</ErrorBoundary>;
}
export default function App() {
const { checkSession, isAuthenticated } = useAuthStore();
@@ -39,30 +58,32 @@ export default function App() {
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/login" element={
isAuthenticated ? <Navigate to="/" replace /> : <LoginPage />
isAuthenticated ? <Navigate to="/" replace /> : <PageErrorBoundary><LoginPage /></PageErrorBoundary>
} />
<Route path="/invite/:token" element={<InvitePage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
<Route path="/invite/:token" element={<PageErrorBoundary><InvitePage /></PageErrorBoundary>} />
<Route path="/forgot-password" element={<PageErrorBoundary><ForgotPasswordPage /></PageErrorBoundary>} />
<Route path="/reset-password/:token" element={<PageErrorBoundary><ResetPasswordPage /></PageErrorBoundary>} />
<Route path="/" element={
<ProtectedRoute>
<Layout />
</ProtectedRoute>
}>
<Route index element={<DashboardPage />} />
<Route path="clients" element={<ClientsPage />} />
<Route path="clients/:id" element={<ClientDetailPage />} />
<Route path="events" element={<EventsPage />} />
<Route path="emails" element={<EmailsPage />} />
<Route path="network" element={<NetworkPage />} />
<Route path="reports" element={<ReportsPage />} />
<Route path="templates" element={<TemplatesPage />} />
<Route path="segments" element={<SegmentsPage />} />
<Route path="settings" element={<SettingsPage />} />
<Route path="admin" element={<AdminPage />} />
<Route index element={<PageErrorBoundary><DashboardPage /></PageErrorBoundary>} />
<Route path="clients" element={<PageErrorBoundary><ClientsPage /></PageErrorBoundary>} />
<Route path="clients/:id" element={<PageErrorBoundary><ClientDetailPage /></PageErrorBoundary>} />
<Route path="events" element={<PageErrorBoundary><EventsPage /></PageErrorBoundary>} />
<Route path="emails" element={<PageErrorBoundary><EmailsPage /></PageErrorBoundary>} />
<Route path="network" element={<PageErrorBoundary><NetworkPage /></PageErrorBoundary>} />
<Route path="reports" element={<PageErrorBoundary><ReportsPage /></PageErrorBoundary>} />
<Route path="templates" element={<PageErrorBoundary><TemplatesPage /></PageErrorBoundary>} />
<Route path="segments" element={<PageErrorBoundary><SegmentsPage /></PageErrorBoundary>} />
<Route path="settings" element={<PageErrorBoundary><SettingsPage /></PageErrorBoundary>} />
<Route path="admin" element={<PageErrorBoundary><AdminPage /></PageErrorBoundary>} />
<Route path="audit-log" element={<PageErrorBoundary><AuditLogPage /></PageErrorBoundary>} />
</Route>
</Routes>
</Suspense>
<ToastContainer />
</BrowserRouter>
);
}