feat: audit log page, meeting prep modal, communication style, error boundaries + toast

- 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

@@ -17,6 +17,7 @@ import ClientForm from '@/components/ClientForm';
import EmailComposeModal from '@/components/EmailComposeModal';
import ClientNotes from '@/components/ClientNotes';
import LogInteractionModal from '@/components/LogInteractionModal';
import MeetingPrepModal from '@/components/MeetingPrepModal';
import type { Interaction } from '@/types';
export default function ClientDetailPage() {
@@ -31,6 +32,7 @@ export default function ClientDetailPage() {
const [showEdit, setShowEdit] = useState(false);
const [showCompose, setShowCompose] = useState(false);
const [showLogInteraction, setShowLogInteraction] = useState(false);
const [showMeetingPrep, setShowMeetingPrep] = useState(false);
const [deleting, setDeleting] = useState(false);
const { togglePin, isPinned } = usePinnedClients();
@@ -120,6 +122,10 @@ export default function ClientDetailPage() {
<Phone className="w-4 h-4" />
<span className="hidden sm:inline">Log Interaction</span>
</button>
<button onClick={() => setShowMeetingPrep(true)} className="flex items-center gap-2 px-3 py-2 bg-purple-50 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300 rounded-lg text-sm font-medium hover:bg-purple-100 dark:hover:bg-purple-900/50 transition-colors">
<Briefcase className="w-4 h-4" />
<span className="hidden sm:inline">Meeting Prep</span>
</button>
<button onClick={() => setShowCompose(true)} className="flex items-center gap-2 px-3 py-2 bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 rounded-lg text-sm font-medium hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors">
<Sparkles className="w-4 h-4" />
<span className="hidden sm:inline">Generate Email</span>
@@ -355,6 +361,14 @@ export default function ClientDetailPage() {
onGenerated={(email) => setEmails((prev) => [email, ...prev])}
/>
{/* Meeting Prep Modal */}
<MeetingPrepModal
isOpen={showMeetingPrep}
onClose={() => setShowMeetingPrep(false)}
clientId={client.id}
clientName={`${client.firstName} ${client.lastName}`}
/>
{/* Log Interaction Modal */}
<LogInteractionModal
isOpen={showLogInteraction}