Frontend Components Documentation¶
Overview¶
This document provides comprehensive documentation for the Next.js frontend application components, hooks, state management, and architectural patterns used in the HG Content Generation Platform.
Table of Contents¶
- Project Structure
- UI Component Library
- React Hooks
- State Management (Zustand)
- Form Components and Validation
- Dashboard Components
- Analytics Components
- Page Structure and Routing
- Real-time Features (Supabase)
- TanStack Query Integration
- Best Practices
Project Structure¶
apps/frontend/
├── app/ # App Router (Next.js 13+)
│ ├── (auth)/ # Route groups for authentication
│ │ ├── login/
│ │ └── register/
│ ├── (dashboard)/ # Protected dashboard routes
│ │ ├── analytics/
│ │ ├── content/
│ │ ├── jobs/
│ │ ├── settings/
│ │ └── strategies/
│ └── api/ # API routes
├── components/ # Legacy components and shared UI
│ └── ui/ # Shadcn/ui components
├── src/
│ ├── components/ # Application components
│ │ ├── analytics/ # Analytics dashboard components
│ │ ├── content/ # Content-related components
│ │ ├── dashboard/ # Dashboard components
│ │ ├── forms/ # Form components
│ │ ├── jobs/ # Job management components
│ │ └── shared/ # Shared utility components
│ ├── hooks/ # Custom React hooks
│ ├── stores/ # Zustand stores
│ ├── types/ # TypeScript type definitions
│ └── lib/ # Utilities and validators
├── lib/ # Legacy lib folder
└── styles/ # Global styles and Tailwind CSS
UI Component Library¶
The application uses Shadcn/ui components built on Radix UI primitives with Tailwind CSS styling.
Core UI Components¶
Button Component¶
// Location: /components/ui/button.tsx
import { Button } from '@/components/ui/button'
// Variants: default, destructive, outline, secondary, ghost, link
// Sizes: default, sm, lg, icon
<Button variant="default" size="lg">
Generate Content
</Button>
Form Components¶
// Location: /components/ui/form.tsx
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
// Integrated with react-hook-form and zod validation
<Form {...form}>
<FormField
control={form.control}
name="topic"
render={({ field }) => (
<FormItem>
<FormLabel>Topic</FormLabel>
<FormControl>
<Input placeholder="Enter topic..." {...field} />
</FormControl>
<FormDescription>
The main subject of your content
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</Form>
Card Component¶
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
<Card>
<CardHeader>
<CardTitle>Analytics Overview</CardTitle>
</CardHeader>
<CardContent>
{/* Card content */}
</CardContent>
</Card>
Specialized UI Components¶
Loading States¶
// Location: /components/ui/loading-states.tsx
import { PageSkeleton, StatsSkeleton, ChartSkeleton } from '@/components/ui/loading-states'
// Usage for different loading scenarios
{isLoading ? <StatsSkeleton /> : <StatsComponent />}
Error States¶
// Location: /components/ui/error-states.tsx
import { ErrorState } from '@/components/ui/error-states'
<ErrorState
title="Failed to load data"
description="Please try again later"
action={<Button onClick={refetch}>Retry</Button>}
/>
React Hooks¶
useJob Hook¶
Real-time job monitoring with Supabase subscriptions.
// Location: /src/hooks/useJob.ts
import { useJob } from '@/src/hooks/useJob'
function JobDetail({ jobId }: { jobId: string }) {
const {
job,
isLoading,
isError,
error,
refetch,
isConnected,
cleanup
} = useJob(jobId, {
enabled: true,
refetchInterval: 5000, // Poll every 5 seconds
onUpdate: (updatedJob) => {
toast.success(`Job ${updatedJob.id} updated`)
},
onError: (error) => {
toast.error(`Job error: ${error.message}`)
}
})
if (isLoading) return <div>Loading job...</div>
if (isError) return <div>Error: {error?.message}</div>
return (
<div>
<div>Status: {job?.status}</div>
<div>Real-time: {isConnected ? '🟢' : '🔴'}</div>
</div>
)
}
Features: - Real-time updates via Supabase subscriptions - Automatic cleanup on unmount - Error handling and retry logic - Connection status monitoring - TanStack Query integration
useJobs Hook¶
Multi-job management with real-time updates.
// Location: /src/hooks/useJobs.ts
import { useJobs } from '@/src/hooks/useJobs'
function JobsList() {
const {
jobs,
isLoading,
isError,
error,
refetch,
isConnected
} = useJobs({
limit: 20,
onInsert: (newJob) => {
toast.success(`New job created: ${newJob.topic}`)
},
onUpdate: (updatedJob) => {
if (updatedJob.status === 'completed') {
toast.success(`Job completed: ${updatedJob.topic}`)
}
}
})
return (
<div>
{jobs.map(job => (
<JobCard key={job.id} job={job} />
))}
</div>
)
}
useAnalytics Hook¶
Analytics data fetching with filtering capabilities.
// Location: /src/hooks/useAnalytics.ts
import { useAnalytics } from '@/src/hooks/useAnalytics'
function AnalyticsDashboard() {
const { data: analytics, isLoading, error } = useAnalytics({
startDate: '2024-01-01',
endDate: '2024-12-31',
clientId: currentClient?.id,
enabled: !!currentClient
})
return (
<div>
<h2>Total Content: {analytics?.totalContent}</h2>
<h2>Total Spent: ${analytics?.totalSpent}</h2>
<h2>Success Rate: {analytics?.successRate}%</h2>
</div>
)
}
useFormPersistence Hook¶
Advanced form state persistence with auto-save and draft management.
// Location: /src/hooks/useFormPersistence.ts
import { useFormPersistence } from '@/src/hooks/useFormPersistence'
function ContentForm() {
const form = useForm<ContentRequestPayload>({
resolver: zodResolver(ContentRequestValidator)
})
const {
drafts,
currentDraftId,
hasUnsavedChanges,
isAutoSaving,
lastSaveTime,
saveManualDraft,
loadAndRestoreDraft,
removeDraft,
newDraft
} = useFormPersistence({
form,
autoSaveInterval: 30000, // 30 seconds
enableAutoSave: true,
onDraftRestored: (draft) => {
toast.success(`Draft "${draft.name}" restored`)
},
onAutoSave: (draftId) => {
console.log('Auto-saved:', draftId)
}
})
return (
<div>
<div className="flex justify-between items-center">
<h2>Content Request Form</h2>
<div className="flex items-center gap-2">
{isAutoSaving && <span>Saving...</span>}
{hasUnsavedChanges && <span>Unsaved changes</span>}
<Button onClick={() => saveManualDraft('My Draft')}>
Save Draft
</Button>
</div>
</div>
{/* Draft selector */}
<Select onValueChange={loadAndRestoreDraft}>
<SelectTrigger>
<SelectValue placeholder="Load draft..." />
</SelectTrigger>
<SelectContent>
{drafts.map(draft => (
<SelectItem key={draft.id} value={draft.id}>
{draft.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Form {...form}>
{/* Form fields */}
</Form>
</div>
)
}
State Management (Zustand)¶
useContentStore¶
Global application state management with Zustand and Immer.
// Location: /src/stores/useContentStore.ts
import { useContentStore } from '@/src/stores/useContentStore'
// Store structure
interface ContentState {
currentClient: Client | null
clients: Client[]
jobs: Job[]
isLoadingClients: boolean
error: string | null
}
// Usage examples
function ClientSelector() {
const {
currentClient,
clients,
isLoadingClients,
fetchClients,
setCurrentClient
} = useContentStore()
useEffect(() => {
fetchClients()
}, [fetchClients])
return (
<Select
value={currentClient?.id}
onValueChange={(id) => {
const client = clients.find(c => c.id === id)
if (client) setCurrentClient(client)
}}
>
<SelectTrigger>
<SelectValue placeholder="Select client..." />
</SelectTrigger>
<SelectContent>
{clients.map(client => (
<SelectItem key={client.id} value={client.id}>
{client.name}
</SelectItem>
))}
</SelectContent>
</Select>
)
}
Store Features: - Client management and switching - Job tracking and real-time updates - Persistent storage (localStorage) - Error handling - Optimistic updates - Supabase integration
Form Components and Validation¶
ContentRequestForm¶
Advanced form component with comprehensive validation and features.
// Location: /src/components/forms/ContentRequestForm.tsx
import { ContentRequestForm } from '@/src/components/forms/ContentRequestForm'
// Features:
// - Zod validation with ContentRequestValidator
// - Draft management and auto-save
// - Real-time validation feedback
// - Progressive disclosure UI
// - Keyword management
// - SEO optimization fields
// - Target audience definition
// - Content structure configuration
<ContentRequestForm
onSubmit={(data) => {
console.log('Form submitted:', data)
}}
initialData={existingDraft}
enableDrafts={true}
enableAutoSave={true}
/>
Form Validation Schema¶
// Location: /src/lib/validators/content.ts
import { ContentRequestValidator } from '@/src/lib/validators/content'
// Validation features:
const validationFeatures = {
contentTypes: ['blog', 'social', 'local', 'email', 'landing', 'video', 'podcast'],
lengths: ['short', 'medium', 'long', 'extra-long'],
tones: ['professional', 'casual', 'friendly', 'authoritative', 'playful', 'urgent'],
// Advanced validation rules
keywords: {
min: 1,
max: 15,
uniqueness: true,
format: /^[a-zA-Z0-9\s\-_]+$/
},
topic: {
minLength: 3,
maxLength: 200,
noWhitespaceOnly: true
},
urls: {
max: 5,
validUrl: true
}
}
// Usage with react-hook-form
const form = useForm<ContentRequestPayload>({
resolver: zodResolver(ContentRequestValidator),
defaultValues: {
contentType: 'blog',
length: 'medium',
tone: 'professional',
priority: 'medium',
keywords: [{ value: '' }],
contentStructure: {
useHeadings: true,
includeCTA: false,
includeIntroduction: true,
includeConclusion: true,
}
}
})
Specialized Form Components¶
KeywordInput Component¶
// Location: /src/components/forms/KeywordInput.tsx
import { KeywordInput } from '@/src/components/forms/KeywordInput'
<KeywordInput
keywords={keywords}
onChange={setKeywords}
maxKeywords={15}
placeholder="Enter keyword..."
suggestions={['SEO', 'content marketing', 'digital strategy']}
/>
URLListInput Component¶
// Location: /src/components/forms/URLListInput.tsx
import { URLListInput } from '@/src/components/forms/URLListInput'
<URLListInput
urls={referenceUrls}
onChange={setReferenceUrls}
maxUrls={5}
placeholder="https://example.com"
validateUrl={true}
/>
Dashboard Components¶
RecentJobs Component¶
Real-time job monitoring dashboard.
// Location: /src/components/dashboard/RecentJobs.tsx
import { RecentJobs } from '@/src/components/dashboard/RecentJobs'
<RecentJobs
limit={10}
showFilters={true}
enableRealTime={true}
onJobClick={(job) => router.push(`/content/${job.id}`)}
/>
ClientManagement Component¶
Multi-client management interface.
// Location: /src/components/dashboard/ClientManagement.tsx
import { ClientManagement } from '@/src/components/dashboard/ClientManagement'
<ClientManagement
onClientSelect={(client) => setCurrentClient(client)}
enableCreate={true}
enableEdit={true}
/>
QuickStats Component¶
Dashboard statistics overview.
// Location: /src/components/dashboard/QuickStats.tsx
import { QuickStats } from '@/src/components/dashboard/QuickStats'
<QuickStats
period="week" // "day" | "week" | "month" | "year"
clientId={currentClient?.id}
showComparison={true}
/>
Analytics Components¶
AnalyticsDashboard¶
Comprehensive analytics dashboard with multiple visualization types.
// Location: /src/components/analytics/AnalyticsDashboard.tsx
import { AnalyticsDashboard } from '@/src/components/analytics/AnalyticsDashboard'
<AnalyticsDashboard
className="space-y-6"
/>
Features: - Cost tracking and analysis - Performance metrics - Content type distribution - Client breakdown - Time-series analysis - Interactive charts (Recharts) - Date range filtering - Export capabilities
Chart Components¶
Cost Analysis Charts¶
// Location: /src/components/analytics/CostCharts.tsx
import { DailyCostsChart, CostByProviderChart } from '@/src/components/analytics/CostCharts'
<DailyCostsChart
data={analytics?.dailyCosts}
height={300}
showTrend={true}
/>
<CostByProviderChart
data={analytics?.costByProvider}
height={400}
showPercentages={true}
/>
Performance Charts¶
// Location: /src/components/analytics/ChartComponents.tsx
import {
JobsOverTimeChart,
ContentTypeChart,
StatusDistributionChart,
ClientBreakdownChart
} from '@/src/components/analytics/ChartComponents'
<JobsOverTimeChart data={analytics?.jobsOverTime} />
<ContentTypeChart data={analytics?.contentByType} />
<StatusDistributionChart data={analytics?.statusDistribution} />
<ClientBreakdownChart data={analytics?.clientBreakdown} />
MetricsCards Component¶
Key performance indicators display.
// Location: /src/components/analytics/MetricsCards.tsx
import { MetricsCards } from '@/src/components/analytics/MetricsCards'
<MetricsCards
totalSpent={analytics?.totalSpent}
totalContent={analytics?.totalContent}
avgCostPerContent={analytics?.avgCostPerContent}
successRate={analytics?.successRate}
showComparison={true}
comparisonPeriod="previous_month"
/>
Page Structure and Routing¶
App Router Structure (Next.js 13+)¶
// Root layout: /app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<ErrorBoundary showDetails={true}>
<Providers>
{children}
<Toaster position="top-right" />
</Providers>
</ErrorBoundary>
</body>
</html>
)
}
Route Groups¶
Authentication Routes (auth)¶
/app/(auth)/
├── layout.tsx # Auth-specific layout
├── login/page.tsx # Login page
└── register/page.tsx # Registration page
Dashboard Routes (dashboard)¶
/app/(dashboard)/
├── layout.tsx # Dashboard layout with navigation
├── page.tsx # Dashboard home
├── analytics/page.tsx # Analytics dashboard
├── content/
│ ├── page.tsx # Content overview
│ ├── new/page.tsx # New content form
│ ├── history/page.tsx # Content history
│ └── [jobId]/page.tsx # Job details
├── jobs/page.tsx # Jobs management
├── settings/
│ ├── page.tsx # Settings overview
│ └── components/ # Settings components
└── strategies/page.tsx # Strategy management
Middleware Authentication¶
// Location: /middleware.ts
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareClient({ req, res })
const { data: { session } } = await supabase.auth.getSession()
// Protected routes
const protectedPaths = ['/dashboard', '/content', '/analytics', '/settings', '/strategies']
const isProtectedPath = protectedPaths.some(path => req.nextUrl.pathname.startsWith(path))
// Redirect logic
if (isProtectedPath && !session) {
return NextResponse.redirect(new URL('/login', req.url))
}
if (authPaths.includes(req.nextUrl.pathname) && session) {
return NextResponse.redirect(new URL('/dashboard', req.url))
}
return res
}
Real-time Features (Supabase)¶
Real-time Job Updates¶
// Real-time subscription pattern used in useJob and useJobs hooks
const channel = supabase
.channel(`job-${jobId}`)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'jobs',
filter: `id=eq.${jobId}`
},
(payload) => {
// Handle real-time updates
const updatedJob = payload.new as Job
queryClient.setQueryData(['job', jobId], updatedJob)
onUpdate?.(updatedJob)
}
)
.subscribe()
// Cleanup
return () => {
channel.unsubscribe()
}
Connection Status Monitoring¶
// Connection status indicator component
function ConnectionStatus() {
const { isConnected } = useJob(jobId)
return (
<div className="flex items-center gap-2">
{isConnected ? (
<>
<Wifi className="h-4 w-4 text-green-500" />
<span className="text-sm text-green-600">Connected</span>
</>
) : (
<>
<WifiOff className="h-4 w-4 text-red-500" />
<span className="text-sm text-red-600">Disconnected</span>
</>
)}
</div>
)
}
TanStack Query Integration¶
Query Client Configuration¶
// Location: /lib/providers.tsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
refetchOnWindowFocus: false,
refetchOnReconnect: true,
},
mutations: {
retry: 1,
},
},
})
Query Patterns¶
Data Fetching¶
// Standard query pattern
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['analytics', { clientId, startDate, endDate }],
queryFn: fetchAnalytics,
enabled: !!clientId,
staleTime: 5 * 60 * 1000,
retry: 2
})
Mutations¶
// Mutation pattern for content generation
const generateContentMutation = useMutation({
mutationFn: async (data: ContentRequestPayload) => {
return useContentStore.getState().generateContent(data)
},
onSuccess: (jobId) => {
toast.success('Content generation started!')
router.push(`/content/${jobId}`)
},
onError: (error) => {
toast.error(`Failed to generate content: ${error.message}`)
}
})
Optimistic Updates¶
// Optimistic updates pattern
const updateJobMutation = useMutation({
mutationFn: updateJob,
onMutate: async (variables) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['job', variables.id] })
// Snapshot previous value
const previousJob = queryClient.getQueryData(['job', variables.id])
// Optimistically update
queryClient.setQueryData(['job', variables.id], variables)
return { previousJob }
},
onError: (err, variables, context) => {
// Rollback on error
if (context?.previousJob) {
queryClient.setQueryData(['job', variables.id], context.previousJob)
}
},
onSettled: (data, error, variables) => {
// Always refetch after mutation
queryClient.invalidateQueries({ queryKey: ['job', variables.id] })
}
})
Best Practices¶
Component Architecture¶
-
Composition over Inheritance
-
Props Interface Design
State Management Guidelines¶
- Use Zustand for Global State
- Client selection
- User authentication state
-
Global UI state (modals, notifications)
-
Use TanStack Query for Server State
- API data fetching
- Caching and synchronization
-
Background updates
-
Use Local State for UI State
- Form inputs
- Modal open/close
- UI interactions
Performance Optimization¶
-
Memoization
-
Code Splitting
-
Query Optimization
// Use appropriate stale times const { data } = useQuery({ queryKey: ['static-data'], queryFn: fetchStaticData, staleTime: Infinity, // Never goes stale }) const { data } = useQuery({ queryKey: ['live-data'], queryFn: fetchLiveData, staleTime: 0, // Always fresh refetchInterval: 1000, // Update every second })
Error Handling¶
-
Error Boundaries
-
Query Error Handling
const { data, error, isError } = useQuery({ queryKey: ['data'], queryFn: fetchData, retry: (failureCount, error) => { // Don't retry 4xx errors if (error.status >= 400 && error.status < 500) { return false } return failureCount < 3 } }) if (isError) { return <ErrorState error={error} onRetry={refetch} /> }
Accessibility¶
-
Semantic HTML
-
ARIA Labels
Testing Considerations¶
-
Component Testing
-
Hook Testing
This comprehensive documentation covers all major aspects of the frontend application architecture, providing developers with the information needed to understand, maintain, and extend the codebase effectively.