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.tsxfiles 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:
- Contact information (name, email, phone)
- Service type selection
- Document upload (
.docxonly, max 50 MB) - Confirmation and submission
The upload flow:
- Calls
getPresignedUrlwith the filename - Uploads the file directly to S3 via the presigned URL
- Calls
submitIntakewith the form data and S3 key - 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:
- User enters their name and a room name
- Frontend fetches a token from
GET /token - Connects to LiveKit using
@livekit/components-react - 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
- Create a directory under
src/app/matching the desired URL path. - Add a
page.tsxfile as the page component. - If the page needs client-side interactivity, add
'use client'at the top. - Import types from
src/lib/types.tsand API functions fromsrc/lib/api.ts.