diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx
index f1e972f..78e5255 100644
--- a/src/components/Toast.tsx
+++ b/src/components/Toast.tsx
@@ -1,42 +1,11 @@
import { useEffect, useState, useCallback } from 'react';
import { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from 'lucide-react';
import { cn } from '@/lib/utils';
+import { toastListeners, toasts as globalToasts, notifyListeners } from '@/lib/toast';
+import type { ToastItem } from '@/lib/toast';
-export type ToastType = 'success' | 'error' | 'warning' | 'info';
-
-interface ToastItem {
- id: string;
- type: ToastType;
- message: string;
- duration?: number;
-}
-
-// Global toast state
-let toastListeners: ((toasts: ToastItem[]) => void)[] = [];
-let toasts: ToastItem[] = [];
-
-function notifyListeners() {
- toastListeners.forEach(fn => fn([...toasts]));
-}
-
-export function showToast(type: ToastType, message: string, duration = 5000) {
- const id = Math.random().toString(36).slice(2);
- toasts = [...toasts, { id, type, message, duration }];
- notifyListeners();
-
- if (duration > 0) {
- setTimeout(() => {
- toasts = toasts.filter(t => t.id !== id);
- notifyListeners();
- }, duration);
- }
-}
-
-export function toast(message: string) { showToast('info', message); }
-toast.success = (msg: string) => showToast('success', msg);
-toast.error = (msg: string) => showToast('error', msg, 7000);
-toast.warning = (msg: string) => showToast('warning', msg);
-toast.info = (msg: string) => showToast('info', msg);
+// Types and functions re-exported from @/lib/toast for backward compat
+// Import directly from @/lib/toast for non-component usage
const iconMap = {
success: CheckCircle2,
@@ -65,12 +34,14 @@ export function ToastContainer() {
useEffect(() => {
toastListeners.push(setItems);
return () => {
- toastListeners = toastListeners.filter(fn => fn !== setItems);
+ const idx = toastListeners.indexOf(setItems);
+ if (idx >= 0) toastListeners.splice(idx, 1);
};
}, []);
const dismiss = useCallback((id: string) => {
- toasts = toasts.filter(t => t.id !== id);
+ const idx = globalToasts.findIndex(t => t.id === id);
+ if (idx >= 0) globalToasts.splice(idx, 1);
notifyListeners();
}, []);
diff --git a/src/lib/api.ts b/src/lib/api.ts
index f1b02e9..67ec0bc 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -440,27 +440,27 @@ class ApiClient {
}
// Reports & Analytics
- async getReportsOverview(): Promise
{
+ async getReportsOverview(): Promise {
return this.fetch('/reports/overview');
}
- async getReportsGrowth(): Promise {
+ async getReportsGrowth(): Promise {
return this.fetch('/reports/growth');
}
- async getReportsIndustries(): Promise {
+ async getReportsIndustries(): Promise {
return this.fetch('/reports/industries');
}
- async getReportsTags(): Promise {
+ async getReportsTags(): Promise {
return this.fetch('/reports/tags');
}
- async getReportsEngagement(): Promise {
+ async getReportsEngagement(): Promise {
return this.fetch('/reports/engagement');
}
- async getNotificationsLegacy(): Promise {
+ async getNotificationsLegacy(): Promise {
return this.fetch('/reports/notifications');
}
@@ -880,7 +880,7 @@ export interface DuplicateClient {
export interface MergeResult {
success: boolean;
- client: any;
+ client: unknown;
merged: {
fromId: string;
fromName: string;
diff --git a/src/lib/toast.ts b/src/lib/toast.ts
new file mode 100644
index 0000000..7661843
--- /dev/null
+++ b/src/lib/toast.ts
@@ -0,0 +1,36 @@
+export type ToastType = 'success' | 'error' | 'warning' | 'info';
+
+export interface ToastItem {
+ id: string;
+ type: ToastType;
+ message: string;
+ duration?: number;
+}
+
+// Global toast state
+export const toastListeners: ((toasts: ToastItem[]) => void)[] = [];
+export const toasts: ToastItem[] = [];
+
+export function notifyListeners() {
+ toastListeners.forEach(fn => fn([...toasts]));
+}
+
+export function showToast(type: ToastType, message: string, duration = 5000) {
+ const id = Math.random().toString(36).slice(2);
+ toasts.push({ id, type, message, duration });
+ notifyListeners();
+
+ if (duration > 0) {
+ setTimeout(() => {
+ const idx = toasts.findIndex(t => t.id === id);
+ if (idx >= 0) toasts.splice(idx, 1);
+ notifyListeners();
+ }, duration);
+ }
+}
+
+export function toast(message: string) { showToast('info', message); }
+toast.success = (msg: string) => showToast('success', msg);
+toast.error = (msg: string) => showToast('error', msg, 7000);
+toast.warning = (msg: string) => showToast('warning', msg);
+toast.info = (msg: string) => showToast('info', msg);
diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts
index 5115bdd..8f40da8 100644
--- a/src/lib/utils.test.ts
+++ b/src/lib/utils.test.ts
@@ -7,7 +7,8 @@ describe('cn', () => {
});
it('handles conditional classes', () => {
- expect(cn('base', false && 'hidden', 'visible')).toBe('base visible');
+ const isHidden = false;
+ expect(cn('base', isHidden && 'hidden', 'visible')).toBe('base visible');
});
it('merges tailwind conflicts', () => {
diff --git a/src/pages/AuditLogPage.tsx b/src/pages/AuditLogPage.tsx
index 602ba3f..da13236 100644
--- a/src/pages/AuditLogPage.tsx
+++ b/src/pages/AuditLogPage.tsx
@@ -3,10 +3,9 @@ import { api } from '@/lib/api';
import type { AuditLog, User } from '@/types';
import {
Shield, Search, ChevronDown, ChevronRight, ChevronLeft,
- Filter, Calendar, User as UserIcon, Activity,
+ Filter, User as UserIcon, Activity,
} from 'lucide-react';
import { PageLoader } from '@/components/LoadingSpinner';
-import { formatDate } from '@/lib/utils';
const ACTION_COLORS: Record = {
create: 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300',
diff --git a/src/pages/ClientDetailPage.tsx b/src/pages/ClientDetailPage.tsx
index 4081e40..b0360dd 100644
--- a/src/pages/ClientDetailPage.tsx
+++ b/src/pages/ClientDetailPage.tsx
@@ -2,11 +2,11 @@ import { useEffect, useState } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useClientsStore } from '@/stores/clients';
import { api } from '@/lib/api';
-import type { Event, Email, ActivityItem } from '@/types';
+import type { Event, Email, ActivityItem, ClientCreate } from '@/types';
import {
ArrowLeft, Edit3, Trash2, Phone, Mail, MapPin, Building2,
Briefcase, Gift, Heart, Star, Users, Calendar, Send,
- CheckCircle2, Sparkles, Clock, Activity, FileText, UserPlus, RefreshCw, Pin,
+ CheckCircle2, Sparkles, Clock, Activity, FileText, UserPlus, RefreshCw,
} from 'lucide-react';
import { usePinnedClients } from '@/hooks/usePinnedClients';
import { cn, formatDate, getRelativeTime, getInitials } from '@/lib/utils';
@@ -30,7 +30,7 @@ export default function ClientDetailPage() {
const [emails, setEmails] = useState([]);
const [activities, setActivities] = useState([]);
const [activeTab, setActiveTab] = useState<'info' | 'notes' | 'activity' | 'events' | 'emails'>('info');
- const [interactions, setInteractions] = useState([]);
+ const [, setInteractions] = useState([]);
const [showEdit, setShowEdit] = useState(false);
const [showCompose, setShowCompose] = useState(false);
const [showLogInteraction, setShowLogInteraction] = useState(false);
@@ -68,7 +68,7 @@ export default function ClientDetailPage() {
await markContacted(client.id);
};
- const handleUpdate = async (data: any) => {
+ const handleUpdate = async (data: ClientCreate) => {
await updateClient(client.id, data);
setShowEdit(false);
};
@@ -110,7 +110,7 @@ export default function ClientDetailPage() {
const stages = ['lead', 'prospect', 'onboarding', 'active', 'inactive'];
const currentIdx = stages.indexOf(client.stage || 'lead');
const nextStage = stages[(currentIdx + 1) % stages.length];
- await updateClient(client.id, { stage: nextStage } as any);
+ await updateClient(client.id, { stage: nextStage as ClientCreate['stage'] });
}} />
{client.tags && client.tags.length > 0 && (
client.tags.map((tag) => {tag})
diff --git a/src/pages/ClientsPage.tsx b/src/pages/ClientsPage.tsx
index 2f66ae0..1451b3d 100644
--- a/src/pages/ClientsPage.tsx
+++ b/src/pages/ClientsPage.tsx
@@ -1,7 +1,8 @@
import { useEffect, useState, useMemo, useCallback } from 'react';
import { Link, useLocation, useSearchParams } from 'react-router-dom';
import { useClientsStore } from '@/stores/clients';
-import { Search, Plus, Users, X, Upload, LayoutGrid, List, Kanban, ChevronLeft, ChevronRight } from 'lucide-react';
+import type { ClientCreate } from '@/types';
+import { Search, Plus, Users, X, Upload, LayoutGrid, Kanban, ChevronLeft, ChevronRight } from 'lucide-react';
import { cn, getRelativeTime, getInitials } from '@/lib/utils';
import Badge, { StageBadge } from '@/components/Badge';
import EmptyState from '@/components/EmptyState';
@@ -121,7 +122,7 @@ export default function ClientsPage() {
return Array.from(tags).sort();
}, [clients]);
- const handleCreate = async (data: any) => {
+ const handleCreate = async (data: ClientCreate) => {
setCreating(true);
try {
await createClient(data);
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx
index aeb97cf..a97c649 100644
--- a/src/pages/DashboardPage.tsx
+++ b/src/pages/DashboardPage.tsx
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
import { api } from '@/lib/api';
import type { Client, Event, Email, InsightsData } from '@/types';
import type { Interaction } from '@/types';
-import { Users, Calendar, Mail, Plus, ArrowRight, Gift, Heart, Clock, AlertTriangle, Sparkles, UserCheck, PhoneForwarded, Star, Phone, FileText, MoreHorizontal } from 'lucide-react';
+import { Users, Calendar, Mail, Plus, ArrowRight, Gift, Heart, Clock, AlertTriangle, Sparkles, PhoneForwarded, Star, Phone, FileText, MoreHorizontal } from 'lucide-react';
import { formatDate, getDaysUntil, getInitials } from '@/lib/utils';
import { EventTypeBadge } from '@/components/Badge';
import { PageLoader } from '@/components/LoadingSpinner';
diff --git a/src/pages/EmailsPage.tsx b/src/pages/EmailsPage.tsx
index ae2e72c..c7b73c1 100644
--- a/src/pages/EmailsPage.tsx
+++ b/src/pages/EmailsPage.tsx
@@ -32,7 +32,7 @@ export default function EmailsPage() {
await generateEmail(composeForm.clientId, composeForm.purpose, composeForm.provider);
setShowCompose(false);
setComposeForm({ clientId: '', purpose: '', provider: 'anthropic' });
- } catch {}
+ } catch { /* silently handled */ }
};
const handleGenerateBirthday = async () => {
@@ -40,7 +40,7 @@ export default function EmailsPage() {
await generateBirthdayEmail(composeForm.clientId, composeForm.provider);
setShowCompose(false);
setComposeForm({ clientId: '', purpose: '', provider: 'anthropic' });
- } catch {}
+ } catch { /* silently handled */ }
};
const startEdit = (email: typeof emails[0]) => {
@@ -223,7 +223,7 @@ export default function EmailsPage() {