Frontend Overview

The AuthentiVoice frontend is a modern React application built with TypeScript, providing a responsive and intuitive interface for audio analysis and fraud detection.

Technology Stack

Project Structure

frontend/src/
├── components/          # Reusable UI components
│   ├── ui/             # Base shadcn-ui components
│   ├── dashboard/      # Dashboard-specific components
│   ├── calls/          # Call analysis components
│   ├── auth/           # Authentication components
│   └── landing/        # Landing page sections
├── pages/              # Route-based page components
│   ├── Dashboard.tsx   # Main dashboard
│   ├── Login.tsx       # Authentication pages
│   └── ...
├── hooks/              # Custom React hooks
│   ├── useOrgCallAnalyses.ts
│   ├── useUserRole.ts
│   └── ...
├── lib/                # Utilities and configurations
│   ├── api.ts          # API client
│   ├── supabase.ts     # Supabase client
│   └── utils.ts        # Helper functions
├── types/              # TypeScript type definitions
└── integrations/       # Third-party integrations

Core Components

Layout Components

// components/layout/DashboardLayout.tsx
export function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex h-screen">
      <Sidebar />
      <main className="flex-1 overflow-y-auto">
        <Header />
        <div className="p-6">{children}</div>
      </main>
    </div>
  );
}

Data Components

// components/calls/CallAnalysisCard.tsx
interface CallAnalysisCardProps {
  analysis: CallAnalysis;
  onReview: (id: string) => void;
}

export function CallAnalysisCard({ analysis, onReview }: CallAnalysisCardProps) {
  const fraudScore = analysis.result?.fraud_score || 0;
  const riskLevel = getRiskLevel(fraudScore);
  
  return (
    <Card className={cn("border-2", getRiskBorderColor(riskLevel))}>
      <CardHeader>
        <h3>{analysis.file_name}</h3>
        <Badge variant={getRiskVariant(riskLevel)}>
          {riskLevel} Risk
        </Badge>
      </CardHeader>
      <CardContent>
        {/* Analysis details */}
      </CardContent>
    </Card>
  );
}

State Management

React Query for Server State

// hooks/useOrgCallAnalyses.ts
export function useOrgCallAnalyses() {
  const { organization } = useOrganizations();
  
  return useQuery({
    queryKey: ['org', 'callAnalysis', organization?.id],
    queryFn: async () => {
      const response = await listCallAnalyses({
        limit: 100,
        offset: 0,
      }, { orgId: organization?.id });
      
      return transformApiCallAnalyses(response.items);
    },
    staleTime: 1000 * 5,
    refetchInterval: 1000 * 10,
  });
}

Local State with Zustand (if needed)

// stores/uiStore.ts
interface UIStore {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
}

export const useUIStore = create<UIStore>((set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
}));

Routing

Route Configuration

// App.tsx
<BrowserRouter>
  <Routes>
    <Route path="/" element={<Landing />} />
    <Route path="/login" element={<Login />} />
    <Route path="/dashboard" element={
      <ProtectedRoute>
        <Dashboard />
      </ProtectedRoute>
    } />
    <Route path="/dashboard/metrics" element={
      <ProtectedRoute requiredRole="admin">
        <Metrics />
      </ProtectedRoute>
    } />
  </Routes>
</BrowserRouter>

Protected Routes

// components/auth/ProtectedRoute.tsx
export function ProtectedRoute({ 
  children, 
  requiredRole 
}: { 
  children: ReactNode;
  requiredRole?: UserRole;
}) {
  const { user, loading } = useAuth();
  const { role } = useUserRole();
  
  if (loading) return <LoadingSpinner />;
  if (!user) return <Navigate to="/login" />;
  if (requiredRole && role !== requiredRole) {
    return <Navigate to="/dashboard" />;
  }
  
  return <>{children}</>;
}

API Integration

API Client Configuration

// lib/api.ts
const getApiBaseUrl = (): string => {
  // Runtime config takes precedence
  if (window.__RUNTIME_CONFIG__?.VITE_API_BASE_URL) {
    return window.__RUNTIME_CONFIG__.VITE_API_BASE_URL;
  }
  
  // Fall back to build-time env
  return import.meta.env.VITE_API_BASE_URL || "https://api.authentivoice.com";
};

async function request<T>(
  path: string,
  options: RequestOptions = {}
): Promise<T> {
  const url = `${getApiBaseUrl()}${path}`;
  
  // Include auth token
  const { data: { session } } = await supabase.auth.getSession();
  
  const response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${session?.access_token}`,
      'x-api-key': getApiKey(),
      ...options.headers,
    },
  });
  
  if (!response.ok) {
    throw new ApiError(response);
  }
  
  return response.json();
}

Authentication

Supabase Authentication

// contexts/AuthContext.tsx
export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Check current session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
      setLoading(false);
    });
    
    // Listen for auth changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null);
      }
    );
    
    return () => subscription.unsubscribe();
  }, []);
  
  return (
    <AuthContext.Provider value={{ user, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

Performance Optimizations

Code Splitting

// Lazy load heavy components
const AnalysisDetail = lazy(() => import('./pages/AnalysisDetail'));

// In your routes
<Route path="/analysis/:id" element={
  <Suspense fallback={<LoadingSpinner />}>
    <AnalysisDetail />
  </Suspense>
} />

Memoization

// Memoize expensive components
export const ExpensiveChart = memo(({ data }: { data: ChartData }) => {
  const processedData = useMemo(() => 
    processChartData(data), [data]
  );
  
  return <Chart data={processedData} />;
});

Virtual Scrolling

// For long lists
import { VirtualList } from '@tanstack/react-virtual';

export function CallAnalysisList({ items }: { items: CallAnalysis[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 120,
  });
  
  return (
    <div ref={parentRef} className="h-full overflow-auto">
      {virtualizer.getVirtualItems().map((virtualItem) => (
        <CallAnalysisCard 
          key={items[virtualItem.index].id}
          analysis={items[virtualItem.index]} 
        />
      ))}
    </div>
  );
}

Testing Strategy

Component Testing

// __tests__/CallAnalysisCard.test.tsx
import { render, screen } from '@testing-library/react';
import { CallAnalysisCard } from '@/components/calls/CallAnalysisCard';

describe('CallAnalysisCard', () => {
  it('displays high risk badge for high fraud scores', () => {
    const analysis = {
      id: '1',
      file_name: 'test.mp3',
      result: { fraud_score: 0.9 },
    };
    
    render(<CallAnalysisCard analysis={analysis} />);
    
    expect(screen.getByText('High Risk')).toBeInTheDocument();
  });
});

Hook Testing

// __tests__/useOrgCallAnalyses.test.tsx
import { renderHook, waitFor } from '@testing-library/react';
import { useOrgCallAnalyses } from '@/hooks/useOrgCallAnalyses';

describe('useOrgCallAnalyses', () => {
  it('fetches and transforms call analyses', async () => {
    const { result } = renderHook(() => useOrgCallAnalyses());
    
    await waitFor(() => {
      expect(result.current.callAnalyses).toHaveLength(3);
    });
  });
});

Build and Deployment

Production Build

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom'],
          'ui-vendor': ['@radix-ui/*'],
        },
      },
    },
  },
  // Enable source maps for production debugging
  build: {
    sourcemap: true,
  },
});

Environment Configuration

// Runtime configuration support
declare global {
  interface Window {
    __RUNTIME_CONFIG__: {
      VITE_API_BASE_URL?: string;
      VITE_SUPABASE_URL?: string;
      VITE_SUPABASE_ANON_KEY?: string;
    };
  }
}

Best Practices

  • Keep components small and focused
  • Use composition over inheritance
  • Implement proper prop validation with TypeScript
  • Follow naming conventions (PascalCase for components)
  • Use React Query for server state
  • Keep local state close to where it’s used
  • Avoid prop drilling with Context API
  • Consider Zustand for complex client state
  • Implement code splitting for large features
  • Use React.memo for expensive components
  • Virtualize long lists
  • Optimize bundle size with tree shaking
  • Never expose sensitive keys in frontend code
  • Validate all user inputs
  • Implement proper CORS handling
  • Use HTTPS in production