Frontend Guide

The frontend is a Next.js application using the App Router, located at platform/frontend/.

Project Structure

platform/frontend/
├── next.config.js
├── package.json
├── public/
├── src/
│   ├── app/                  # App Router pages and layouts
│   │   ├── layout.tsx        # Root layout
│   │   ├── page.tsx          # Home page
│   │   ├── submit/
│   │   │   └── page.tsx      # Dissertation submission form
│   │   ├── quote/
│   │   │   └── [id]/
│   │   │       └── page.tsx  # Quote display (dynamic route)
│   │   └── room/
│   │       └── page.tsx      # Video consultation room
│   ├── components/           # Reusable UI components
│   ├── lib/
│   │   ├── api.ts            # API client for backend calls
│   │   └── types.ts          # TypeScript type definitions
│   └── styles/
└── tsconfig.json

App Router Conventions

This project uses Next.js App Router (not Pages Router). Key conventions:

  • Server Components by default: All components in app/ are React Server Components unless marked with 'use client'.
  • Client Components: Components that use browser APIs, hooks (useState, useEffect), or event handlers must have 'use client' at the top of the file.
  • Layouts: layout.tsx files wrap child routes and persist across navigation.
  • Dynamic routes: Use [param] folder naming (e.g., quote/[id]/page.tsx).

When to Use Client Components

Add 'use client' when the component:

  • Uses React hooks (useState, useEffect, useRef, etc.)
  • Handles user events (onClick, onChange, onSubmit)
  • Uses browser-only APIs (window, navigator, localStorage)
  • Uses LiveKit components (they require browser APIs)
'use client';

import { useState } from 'react';

export default function SubmitForm() {
  const [name, setName] = useState('');
  // ...
}

API Client

The API client in src/lib/api.ts centralizes all backend communication:

const API_BASE = process.env.NEXT_PUBLIC_API_URL ||
  'https://jwfqmc0638.execute-api.us-east-1.amazonaws.com';

export async function getPresignedUrl(fileName: string) {
  const res = await fetch(`${API_BASE}/intake/presigned-url`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ fileName }),
  });
  return res.json();
}

export async function submitIntake(data: IntakeFormData) {
  const res = await fetch(`${API_BASE}/intake/submit`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  return res.json();
}

export async function getSubmission(submissionId: string) {
  const res = await fetch(`${API_BASE}/intake/submission/${submissionId}`);
  return res.json();
}

Type Definitions

Types are defined in src/lib/types.ts and shared across the application:

export interface IntakeFormData {
  submissionId: string;
  name: string;
  email: string;
  phone?: string;
  serviceType: string;
  s3Key: string;
  notes?: string;
}

export interface DocumentAnalysis {
  textWordCount: number;
  refsWordCount: number;
  tables: number;
  figures: number;
  frontMatter: number;
}

export interface QuoteLineItem {
  description: string;
  amount: number;
}

export interface Quote {
  lineItems: QuoteLineItem[];
  subtotal: number;
  adminFee: number;
  total: number;
}

export interface Submission {
  submissionId: string;
  status: 'processing' | 'completed' | 'error';
  name: string;
  email: string;
  serviceType: string;
  createdAt: string;
  analysis?: DocumentAnalysis;
  quote?: Quote;
  pipedriveUrl?: string;
}

Key Pages

Submit Page (/submit)

A multi-step client component form:

  1. Contact information (name, email, phone)
  2. Service type selection
  3. Document upload (.docx only, max 50 MB)
  4. Confirmation and submission

The upload flow:

  1. Calls getPresignedUrl with the filename
  2. Uploads the file directly to S3 via the presigned URL
  3. Calls submitIntake with the form data and S3 key
  4. Redirects to the quote page

Quote Page (/quote/[id])

Polls getSubmission every 2-3 seconds until the status changes from processing. Displays:

  • A loading/processing indicator while analysis runs
  • The full quote with line items on completion
  • An error message if processing failed

Room Page (/room)

A video consultation interface using LiveKit components:

  1. User enters their name and a room name
  2. Frontend fetches a token from GET /token
  3. Connects to LiveKit using @livekit/components-react
  4. Renders video/audio tracks for all participants

Styling

The project uses Tailwind CSS for styling. Global styles are in src/styles/globals.css. Component-level styles use Tailwind utility classes directly in JSX.

Adding a New Page

  1. Create a directory under src/app/ matching the desired URL path.
  2. Add a page.tsx file as the page component.
  3. If the page needs client-side interactivity, add 'use client' at the top.
  4. Import types from src/lib/types.ts and API functions from src/lib/api.ts.