Overview
This page documents the main custom components built for the Argument Analysis Tool. These components combine shadcn/ui primitives with application-specific logic.
AnalysisAndSocialLayout
The main layout component that displays the argument analysis alongside social media pulse data.
Location : src/components/analysis/AnalysisAndSocialLayout.tsx
Props
The complete analysis result including blueprint, summary, analysis, social pulse, and tweets
Callback function to reset the analysis and return to the input form
Type Definition
interface AnalysisAndSocialLayoutProps {
analysisData : AnalysisResult ;
onReset : () => void ;
}
type AnalysisResult = {
blueprint : ArgumentNode [];
summary : string ;
analysis : string ;
socialPulse : string ;
tweets : Tweet [];
};
Usage Example
import { AnalysisAndSocialLayout } from '@/components/analysis/AnalysisAndSocialLayout' ;
export default function Home () {
const [ state , formAction , isPending ] = useActionState ( handleAnalysis , initialState );
if ( state . data ) {
return (
< AnalysisAndSocialLayout
analysisData = { state . data }
onReset = { () => window . location . reload () }
/>
);
}
// ... render input form
}
Features
Layout
State Management
Child Components
Responsive Split View The component creates a two-column grid layout: < div className = { cn (
"grid h-[calc(100vh-80px)] w-full transition-all duration-300" ,
isSocialOpen ? "grid-cols-[1fr,384px]" : "grid-cols-[1fr,0px]"
) } >
{ /* Analysis view on left */ }
{ /* Social view on right */ }
</ div >
Left column: Analysis visualizations (flexible width)
Right column: Social pulse (384px fixed width)
Smooth transitions when toggling social panel
Toggle State Manages the visibility of the social panel: const [ isSocialOpen , setIsSocialOpen ] = useState ( true );
Both child components receive:
isSocialOpen - Current state
onSocialToggle - Toggle callback
This allows the toggle button to appear in the toolbar while controlling the layout. Composition Renders two main child components: AnalysisView
Displays argument visualizations
Contains toolbar and view switcher
Receives analysis data and callbacks
SocialView
Shows social media pulse analysis
Displays related tweets
Collapsible with smooth animation
Component Tree
AnalysisAndSocialLayout
├── AnalysisView
│ ├── AnalysisToolbar
│ │ ├── View mode buttons
│ │ ├── Export button
│ │ ├── Social toggle
│ │ └── Reset button
│ └── Visualization views
│ ├── BalancedView
│ ├── TreeView
│ ├── PillarView
│ ├── CircularView
│ └── FlowchartView
└── SocialView
├── Social pulse analysis
└── TweetCard (multiple)
The main input form for analyzing topics, URLs, or documents.
Location : src/components/home/InputForm.tsx
Props
formAction
(formData: FormData) => void
required
Server action to handle form submission and trigger analysis
Firebase authentication token for API requests
Type Definition
type InputType = 'Topic' | 'URL' | 'Document' ;
interface InputFormProps {
formAction : ( formData : FormData ) => void ;
authToken : string | null ;
}
Usage Example
import { InputForm } from '@/components/home/InputForm' ;
import { handleAnalysis } from '@/lib/actions' ;
export default function Home () {
const [ state , formAction , isPending ] = useActionState ( handleAnalysis , initialState );
const [ authToken , setAuthToken ] = useState < string | null >( null );
useEffect (() => {
if ( user ) {
user . getIdToken (). then ( token => setAuthToken ( token ));
}
}, [ user ]);
return < InputForm formAction = { formAction } authToken = { authToken } /> ;
}
Features
The form submits the following data:
formData : {
inputType : 'Topic' | 'URL' | 'Document' ,
input : string , // User input or extracted text
authToken : string | null // Firebase auth token
}
Styling
The form uses the neobrutalism design system:
< Card className = "w-full border-4 shadow-[8px_8px_0px_hsl(var(--border))]" >
{ /* Bold 4px border with hard drop shadow */ }
</ Card >
< TabsTrigger className = "
rounded-sm
border-2
border-transparent
data-[state=active]:border-border
data-[state=active]:bg-background
" >
{ /* Active tab has 2px border */ }
</ TabsTrigger >
LoadingSpinner
A simple, reusable loading spinner component.
Location : src/components/common/LoadingSpinner.tsx
Props
Optional Tailwind classes to customize size and color
Implementation
src/components/common/LoadingSpinner.tsx
import { Loader2 } from "lucide-react" ;
import { cn } from "@/lib/utils" ;
export function LoadingSpinner ({ className } : { className ?: string }) {
return (
< Loader2 className = { cn ( "animate-spin text-accent" , className ) } />
);
}
Usage Examples
Default Size
Custom Size
Custom Color
In Button
Centered
Real Usage
From src/app/page.tsx:
// Loading state while checking authentication
if ( isUserLoading || ! user ) {
return (
< div className = "container mx-auto flex h-[calc(100vh-80px)] max-w-4xl flex-col items-center justify-center p-4" >
< LoadingSpinner className = "h-12 w-12" />
</ div >
);
}
// Loading state during analysis
if ( isPending ) {
return (
< Card className = "w-full max-w-2xl border-4 p-8 text-center" >
< CardContent className = "flex flex-col items-center gap-4 p-0" >
< LoadingSpinner className = "h-12 w-12" />
< h2 className = "font-headline text-3xl font-bold" > Analyzing Arguments... </ h2 >
< p className = "text-muted-foreground" > The AI is deconstructing the source material. </ p >
</ CardContent >
</ Card >
);
}
FirebaseErrorListener
An invisible component that listens for global Firebase permission errors and throws them to be caught by Next.js error boundaries.
Location : src/components/FirebaseErrorListener.tsx
Props
This component accepts no props.
Type Definition
import { FirestorePermissionError } from '@/firebase/errors' ;
// Component returns null (renders nothing)
export function FirebaseErrorListener () : null
Implementation
src/components/FirebaseErrorListener.tsx
'use client' ;
import { useState , useEffect } from 'react' ;
import { errorEmitter } from '@/firebase/error-emitter' ;
import { FirestorePermissionError } from '@/firebase/errors' ;
export function FirebaseErrorListener () {
const [ error , setError ] = useState < FirestorePermissionError | null >( null );
useEffect (() => {
const handleError = ( error : FirestorePermissionError ) => {
setError ( error );
};
errorEmitter . on ( 'permission-error' , handleError );
return () => {
errorEmitter . off ( 'permission-error' , handleError );
};
}, []);
// Throw error to Next.js error boundary
if ( error ) {
throw error ;
}
return null ; // Renders nothing
}
How It Works
Error Event Emission
Firebase operations emit permission errors to a global event emitter: // In Firebase service
catch ( error ) {
errorEmitter . emit ( 'permission-error' , new FirestorePermissionError ());
}
Component Listens
The FirebaseErrorListener subscribes to the ‘permission-error’ event: errorEmitter . on ( 'permission-error' , handleError );
State Update
When an error is received, it updates component state: const handleError = ( error : FirestorePermissionError ) => {
setError ( error );
};
Error Boundary Catch
On re-render, the component throws the error: if ( error ) {
throw error ; // Caught by global-error.tsx
}
Usage
Add to your root layout:
import { FirebaseErrorListener } from '@/components/FirebaseErrorListener' ;
export default function RootLayout ({ children }) {
return (
< html >
< body >
< FirebaseErrorListener />
{ children }
</ body >
</ html >
);
}
Why This Pattern?
Decouples error handling from Firebase operations
Firebase services don’t need to know about React error boundaries. They just emit events: // Firebase service remains clean
export async function getData () {
try {
return await getDoc ( docRef );
} catch ( error ) {
errorEmitter . emit ( 'permission-error' , error );
}
}
Enables global error handling
A single component catches errors from anywhere in the app:
Firestore operations
Firebase Auth operations
Real-time listeners
Cloud Functions calls
All permission errors are routed to Next.js error boundaries.
The event emitter is strongly typed: type ErrorEvents = {
'permission-error' : FirestorePermissionError ;
};
const errorEmitter = new TypedEventEmitter < ErrorEvents >();
TypeScript enforces that listeners receive the correct error type.
The component properly cleans up the event listener: return () => {
errorEmitter . off ( 'permission-error' , handleError );
};
Error Boundary Integration
The thrown error is caught by global-error.tsx:
'use client' ;
export default function GlobalError ({ error , reset }) {
return (
< html >
< body >
< h2 > Permission Error </ h2 >
< p > { error . message } </ p >
< button onClick = { () => reset () } > Try again </ button >
</ body >
</ html >
);
}
Component Composition Patterns
Server vs Client Components
The app uses both server and client components appropriately:
// Server Component (default)
export default async function Page () {
const data = await fetchData ();
return < StaticContent data = { data } /> ;
}
// Client Component (with 'use client')
'use client' ;
export function InteractiveForm () {
const [ value , setValue ] = useState ( '' );
return < Input value = { value } onChange = { e => setValue ( e . target . value ) } /> ;
}
Client Components (require 'use client'):
InputForm - Uses useState, event handlers
AnalysisAndSocialLayout - Uses useState for toggle
FirebaseErrorListener - Uses useEffect, useState
LoadingSpinner - Can be used in both
Compound Components
Many shadcn/ui components use the compound component pattern:
import { Card , CardHeader , CardTitle , CardContent } from '@/components/ui/card' ;
< Card >
< CardHeader >
< CardTitle > Title </ CardTitle >
</ CardHeader >
< CardContent >
Content here
</ CardContent >
</ Card >
This provides:
Flexible composition
Consistent spacing and styling
Clear component hierarchy
Render Props
Some components accept render functions:
< Tabs >
< TabsList >
< TabsTrigger value = "topic" > Topic </ TabsTrigger >
</ TabsList >
< TabsContent value = "topic" >
{ /* Content rendered when active */ }
</ TabsContent >
</ Tabs >
Best Practices
Component Organization
Place reusable components in /common
Feature-specific components in feature folders
UI primitives in /ui
Keep components focused and single-purpose
Props Documentation
Define TypeScript interfaces for props
Use descriptive prop names
Provide default values when sensible
Document complex props with JSDoc
State Management
Keep state as local as possible
Lift state only when needed
Use Server Actions for mutations
Prefer controlled components
Error Handling
Handle errors at component boundaries
Show user-friendly error messages
Use toast notifications for feedback
Implement error boundaries for crashes
Next Steps
Component Library Learn about the component architecture
Server Actions Understand form handling and mutations