Skip to content

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

  1. Project Structure
  2. UI Component Library
  3. React Hooks
  4. State Management (Zustand)
  5. Form Components and Validation
  6. Dashboard Components
  7. Analytics Components
  8. Page Structure and Routing
  9. Real-time Features (Supabase)
  10. TanStack Query Integration
  11. 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

  1. Composition over Inheritance

    // Good: Composable components
    <Card>
      <CardHeader>
        <CardTitle>Analytics</CardTitle>
      </CardHeader>
      <CardContent>
        <MetricsCards data={analytics} />
      </CardContent>
    </Card>
    

  2. Props Interface Design

    interface ComponentProps {
      // Required props first
      data: AnalyticsData
      onUpdate: (data: AnalyticsData) => void
    
      // Optional props with defaults
      className?: string
      showHeader?: boolean
      enableRealTime?: boolean
    
      // Callback props
      onError?: (error: Error) => void
      onSuccess?: (result: any) => void
    }
    

State Management Guidelines

  1. Use Zustand for Global State
  2. Client selection
  3. User authentication state
  4. Global UI state (modals, notifications)

  5. Use TanStack Query for Server State

  6. API data fetching
  7. Caching and synchronization
  8. Background updates

  9. Use Local State for UI State

  10. Form inputs
  11. Modal open/close
  12. UI interactions

Performance Optimization

  1. Memoization

    // Memoize expensive calculations
    const memoizedData = useMemo(() => {
      return processAnalyticsData(rawData)
    }, [rawData])
    
    // Memoize callbacks
    const handleUpdate = useCallback((data) => {
      onUpdate?.(data)
    }, [onUpdate])
    

  2. Code Splitting

    // Lazy load heavy components
    const AnalyticsDashboard = lazy(() => import('@/components/analytics/AnalyticsDashboard'))
    
    // Use in component
    <Suspense fallback={<AnalyticsSkeleton />}>
      <AnalyticsDashboard />
    </Suspense>
    

  3. 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

  1. Error Boundaries

    // Wrap components with error boundaries
    <ErrorBoundary showDetails={process.env.NODE_ENV === 'development'}>
      <Dashboard />
    </ErrorBoundary>
    

  2. 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

  1. Semantic HTML

    // Use appropriate HTML elements
    <main role="main">
      <h1>Dashboard</h1>
      <nav aria-label="Main navigation">
        {/* Navigation items */}
      </nav>
    </main>
    

  2. ARIA Labels

    <Button
      aria-label="Generate content"
      aria-describedby="generate-help"
    >
      <Wand2 className="h-4 w-4" />
    </Button>
    <div id="generate-help" className="sr-only">
      Starts the AI content generation process
    </div>
    

Testing Considerations

  1. Component Testing

    // Test component behavior, not implementation
    test('shows loading state while fetching data', () => {
      render(<JobsList />)
      expect(screen.getByText('Loading...')).toBeInTheDocument()
    })
    

  2. Hook Testing

    // Test custom hooks with renderHook
    const { result } = renderHook(() => useJob('job-123'))
    expect(result.current.isLoading).toBe(true)
    

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.