Files
thrillwiki_django_no_react/docs/frontend.md
pacnpal 04394b9976 Refactor user account system and remove moderation integration
- Remove first_name and last_name fields from User model
- Add user deletion and social provider services
- Restructure auth serializers into separate directory
- Update avatar upload functionality and API endpoints
- Remove django-moderation integration documentation
- Add mandatory compliance enforcement rules
- Update frontend documentation with API usage examples
2025-08-30 07:31:58 -04:00

33 KiB

ThrillWiki Frontend API Documentation

Last updated: 2025-08-29

This document provides comprehensive documentation for all ThrillWiki API endpoints that the NextJS frontend should use.

Authentication

ThrillWiki uses JWT Bearer token authentication. After successful login or signup, you'll receive access and refresh tokens that must be included in subsequent API requests.

Authentication Headers

Include the access token in the Authorization header using Bearer format:

headers: {
  'Authorization': `Bearer ${accessToken}`,
  'Content-Type': 'application/json'
}

Token Management

  • Access Token: Short-lived token (1 hour) used for API requests
  • Refresh Token: Long-lived token (7 days) used to obtain new access tokens
  • Token Rotation: Refresh tokens are rotated on each refresh for enhanced security

Base URL

The frontend uses a Next.js proxy that routes API requests:

  • Frontend requests: /v1/auth/login/, /v1/accounts/profile/, etc.
  • Proxy adds /api/ prefix: /api/v1/auth/login/, /api/v1/accounts/profile/, etc.
  • Backend receives: /api/v1/auth/login/, /api/v1/accounts/profile/, etc.

Important: Frontend code should make requests to /v1/... endpoints, not /api/v1/...

Moderation System API

The moderation system provides comprehensive content moderation, user management, and administrative tools. All moderation endpoints require moderator-level permissions or above.

Moderation Reports

List Reports

  • GET /api/v1/moderation/reports/
  • Permissions: Moderators and above can view all reports, regular users can only view their own reports
  • Query Parameters:
    • status: Filter by report status (PENDING, UNDER_REVIEW, RESOLVED, DISMISSED)
    • priority: Filter by priority (LOW, MEDIUM, HIGH, URGENT)
    • report_type: Filter by report type (SPAM, HARASSMENT, INAPPROPRIATE_CONTENT, etc.)
    • reported_by: Filter by user ID who made the report
    • assigned_moderator: Filter by assigned moderator ID
    • created_after: Filter reports created after date (ISO format)
    • created_before: Filter reports created before date (ISO format)
    • unassigned: Boolean filter for unassigned reports
    • overdue: Boolean filter for overdue reports based on SLA
    • search: Search in reason and description fields
    • ordering: Order by fields (created_at, updated_at, priority, status)

Create Report

  • POST /api/v1/moderation/reports/
  • Permissions: Any authenticated user
  • Body: CreateModerationReportData

Get Report Details

  • GET /api/v1/moderation/reports/{id}/
  • Permissions: Moderators and above, or report creator

Update Report

  • PATCH /api/v1/moderation/reports/{id}/
  • Permissions: Moderators and above
  • Body: Partial UpdateModerationReportData

Assign Report

  • POST /api/v1/moderation/reports/{id}/assign/
  • Permissions: Moderators and above
  • Body: { "moderator_id": number }

Resolve Report

  • POST /api/v1/moderation/reports/{id}/resolve/
  • Permissions: Moderators and above
  • Body: { "resolution_action": string, "resolution_notes": string }

Report Statistics

  • GET /api/v1/moderation/reports/stats/
  • Permissions: Moderators and above
  • Returns: ModerationStatsData

Moderation Queue

List Queue Items

  • GET /api/v1/moderation/queue/
  • Permissions: Moderators and above
  • Query Parameters:
    • status: Filter by status (PENDING, IN_PROGRESS, COMPLETED, CANCELLED)
    • priority: Filter by priority (LOW, MEDIUM, HIGH, URGENT)
    • item_type: Filter by item type (CONTENT_REVIEW, USER_REVIEW, BULK_ACTION, etc.)
    • assigned_to: Filter by assigned moderator ID
    • unassigned: Boolean filter for unassigned items
    • has_related_report: Boolean filter for items with related reports
    • search: Search in title and description fields

Get My Queue

  • GET /api/v1/moderation/queue/my_queue/
  • Permissions: Moderators and above
  • Returns: Queue items assigned to current user

Assign Queue Item

  • POST /api/v1/moderation/queue/{id}/assign/
  • Permissions: Moderators and above
  • Body: { "moderator_id": number }

Unassign Queue Item

  • POST /api/v1/moderation/queue/{id}/unassign/
  • Permissions: Moderators and above

Complete Queue Item

  • POST /api/v1/moderation/queue/{id}/complete/
  • Permissions: Moderators and above
  • Body: CompleteQueueItemData

Moderation Actions

List Actions

  • GET /api/v1/moderation/actions/
  • Permissions: Moderators and above
  • Query Parameters:
    • action_type: Filter by action type (WARNING, USER_SUSPENSION, USER_BAN, etc.)
    • moderator: Filter by moderator ID
    • target_user: Filter by target user ID
    • is_active: Boolean filter for active actions
    • expired: Boolean filter for expired actions
    • expiring_soon: Boolean filter for actions expiring within 24 hours
    • has_related_report: Boolean filter for actions with related reports

Create Action

  • POST /api/v1/moderation/actions/
  • Permissions: Moderators and above (with role-based restrictions)
  • Body: CreateModerationActionData

Get Active Actions

  • GET /api/v1/moderation/actions/active/
  • Permissions: Moderators and above

Get Expired Actions

  • GET /api/v1/moderation/actions/expired/
  • Permissions: Moderators and above

Deactivate Action

  • POST /api/v1/moderation/actions/{id}/deactivate/
  • Permissions: Moderators and above

Bulk Operations

List Bulk Operations

  • GET /api/v1/moderation/bulk-operations/
  • Permissions: Admins and superusers only
  • Query Parameters:
    • status: Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED)
    • operation_type: Filter by operation type
    • priority: Filter by priority
    • created_by: Filter by creator ID
    • can_cancel: Boolean filter for cancellable operations
    • has_failures: Boolean filter for operations with failures
    • in_progress: Boolean filter for operations in progress

Create Bulk Operation

  • POST /api/v1/moderation/bulk-operations/
  • Permissions: Admins and superusers only
  • Body: CreateBulkOperationData

Get Running Operations

  • GET /api/v1/moderation/bulk-operations/running/
  • Permissions: Admins and superusers only

Cancel Operation

  • POST /api/v1/moderation/bulk-operations/{id}/cancel/
  • Permissions: Admins and superusers only

Retry Operation

  • POST /api/v1/moderation/bulk-operations/{id}/retry/
  • Permissions: Admins and superusers only

Get Operation Logs

  • GET /api/v1/moderation/bulk-operations/{id}/logs/
  • Permissions: Admins and superusers only

User Moderation

Get User Moderation Profile

  • GET /api/v1/moderation/users/{id}/
  • Permissions: Moderators and above
  • Returns: UserModerationProfileData

Take Action Against User

  • POST /api/v1/moderation/users/{id}/moderate/
  • Permissions: Moderators and above
  • Body: CreateModerationActionData

Search Users

  • GET /api/v1/moderation/users/search/
  • Permissions: Moderators and above
  • Query Parameters:
    • query: Search in username and email
    • role: Filter by user role
    • has_restrictions: Boolean filter for users with active restrictions

User Moderation Statistics

  • GET /api/v1/moderation/users/stats/
  • Permissions: Moderators and above

Parks API

Parks Listing

  • GET /api/v1/parks/
  • Query Parameters:
    • search: Search in park names and descriptions
    • country: Filter by country code
    • state: Filter by state/province
    • city: Filter by city
    • status: Filter by operational status
    • park_type: Filter by park type
    • has_rides: Boolean filter for parks with rides
    • ordering: Order by fields (name, opened_date, ride_count, etc.)
    • page: Page number for pagination
    • page_size: Number of results per page

Park Details

  • GET /api/v1/parks/{slug}/
  • Returns: Complete park information including rides, photos, and statistics

Park Rides

  • GET /api/v1/parks/{park_slug}/rides/
  • Query Parameters: Similar filtering options as global rides endpoint

Park Photos

  • GET /api/v1/parks/{park_slug}/photos/
  • Query Parameters:
    • photo_type: Filter by photo type (banner, card, gallery)
    • ordering: Order by upload date, likes, etc.

Rides API

Rides Listing

  • GET /api/v1/rides/
  • Query Parameters:
    • search: Search in ride names and descriptions
    • park: Filter by park slug
    • manufacturer: Filter by manufacturer slug
    • ride_type: Filter by ride type
    • status: Filter by operational status
    • opened_after: Filter rides opened after date
    • opened_before: Filter rides opened before date
    • height_min: Minimum height requirement
    • height_max: Maximum height requirement
    • has_photos: Boolean filter for rides with photos
    • ordering: Order by fields (name, opened_date, height, etc.)

Ride Details

  • GET /api/v1/rides/{park_slug}/{ride_slug}/
  • Returns: Complete ride information including specifications, photos, and reviews

Ride Photos

  • GET /api/v1/rides/{park_slug}/{ride_slug}/photos/

Ride Reviews

  • GET /api/v1/rides/{park_slug}/{ride_slug}/reviews/
  • POST /api/v1/rides/{park_slug}/{ride_slug}/reviews/

Manufacturers API

Manufacturers Listing

  • GET /api/v1/rides/manufacturers/
  • Query Parameters:
    • search: Search in manufacturer names
    • country: Filter by country
    • has_rides: Boolean filter for manufacturers with rides
    • ordering: Order by name, ride_count, etc.

Manufacturer Details

  • GET /api/v1/rides/manufacturers/{slug}/

Manufacturer Rides

  • GET /api/v1/rides/manufacturers/{slug}/rides/

Authentication API

Login

  • POST /api/v1/auth/login/
  • Body: { "username": string, "password": string, "turnstile_token"?: string }
  • Returns: JWT tokens and user data
  • Response:
    {
      "access": string,
      "refresh": string,
      "user": {
        "id": number,
        "username": string,
        "email": string,
        "display_name": string,
        "is_active": boolean,
        "date_joined": string
      },
      "message": string
    }
    

Signup

  • POST /api/v1/auth/signup/
  • Body:
    {
      "username": string,
      "email": string,
      "password": string,
      "password_confirm": string,
      "display_name": string,  // Required field
      "turnstile_token"?: string
    }
    
  • Returns: JWT tokens and user data
  • Response: Same format as login response (access and refresh tokens)
  • Note: display_name is now required during registration. The system no longer uses separate first_name and last_name fields.

Token Refresh

  • POST /api/v1/auth/token/refresh/
  • Body: { "refresh": string }
  • Returns: New access token and optionally a new refresh token
  • Response:
    {
      "access": string,
      "refresh"?: string  // Only returned if refresh token rotation is enabled
    }
    

Social Authentication

Google Login

  • POST /api/v1/auth/social/google/
  • Body: { "access_token": string }
  • Returns: JWT tokens and user data (same format as regular login)
  • Note: The access_token should be obtained from Google OAuth flow

Discord Login

  • POST /api/v1/auth/social/discord/
  • Body: { "access_token": string }
  • Returns: JWT tokens and user data (same format as regular login)
  • Note: The access_token should be obtained from Discord OAuth flow

Connect Social Account

  • POST /api/v1/auth/social/{provider}/connect/
  • Permissions: Authenticated users only
  • Body: { "access_token": string }
  • Returns: { "success": boolean, "message": string }
  • Note: Links a social account to an existing ThrillWiki account

Disconnect Social Account

  • POST /api/v1/auth/social/{provider}/disconnect/
  • Permissions: Authenticated users only
  • Returns: { "success": boolean, "message": string }

Get Social Connections

  • GET /api/v1/auth/social/connections/
  • Permissions: Authenticated users only
  • Returns:
    {
      "google": { "connected": boolean, "email"?: string },
      "discord": { "connected": boolean, "username"?: string }
    }
    

Social Provider Management

List Available Providers

  • GET /api/v1/auth/social/providers/available/
  • Permissions: Public access
  • Returns: List of available social providers for connection
  • Response:
    {
      "available_providers": [
        {
          "id": "google",
          "name": "Google",
          "auth_url": "https://example.com/accounts/google/login/",
          "connect_url": "https://example.com/api/v1/auth/social/connect/google/"
        }
      ],
      "count": number
    }
    

List Connected Providers

  • GET /api/v1/auth/social/connected/
  • Permissions: Authenticated users only
  • Returns: List of social providers connected to user's account
  • Response:
    {
      "connected_providers": [
        {
          "provider": "google",
          "provider_name": "Google",
          "uid": "user_id_on_provider",
          "date_joined": "2025-01-01T00:00:00Z",
          "can_disconnect": boolean,
          "disconnect_reason": string | null,
          "extra_data": object
        }
      ],
      "count": number,
      "has_password_auth": boolean,
      "can_disconnect_any": boolean
    }
    

Connect Social Provider

  • POST /api/v1/auth/social/connect/{provider}/
  • Permissions: Authenticated users only
  • Parameters: provider - Provider ID (e.g., 'google', 'discord')
  • Returns: Connection initiation response with auth URL
  • Response:
    {
      "success": boolean,
      "message": string,
      "provider": string,
      "auth_url": string
    }
    
  • Error Responses:
    • 400: Provider already connected or invalid provider
    • 500: Connection initiation failed

Disconnect Social Provider

  • DELETE /api/v1/auth/social/disconnect/{provider}/
  • Permissions: Authenticated users only
  • Parameters: provider - Provider ID to disconnect
  • Returns: Disconnection result with safety information
  • Response:
    {
      "success": boolean,
      "message": string,
      "provider": string,
      "remaining_providers": string[],
      "has_password_auth": boolean,
      "suggestions"?: string[]
    }
    
  • Error Responses:
    • 400: Cannot disconnect (safety validation failed)
    • 404: Provider not connected
    • 500: Disconnection failed

Get Social Authentication Status

  • GET /api/v1/auth/social/status/
  • Permissions: Authenticated users only
  • Returns: Comprehensive social authentication status
  • Response:
    {
      "user_id": number,
      "username": string,
      "email": string,
      "has_password_auth": boolean,
      "connected_providers": ConnectedProvider[],
      "total_auth_methods": number,
      "can_disconnect_any": boolean,
      "requires_password_setup": boolean
    }
    

Social Provider Safety Rules

The social provider management system enforces strict safety rules to prevent users from locking themselves out:

  1. Disconnection Safety: Users can only disconnect a social provider if they have:

    • Another social provider connected, OR
    • Email + password authentication set up
  2. Error Scenarios:

    • Only Provider + No Password: "Cannot disconnect your only authentication method. Please set up a password or connect another social provider first."
    • No Password Auth: "Please set up email/password authentication before disconnecting this provider."
  3. Suggested Actions: When disconnection is blocked, the API provides suggestions:

    • "Set up password authentication"
    • "Connect another social provider"

Usage Examples

Check Social Provider Status

const checkSocialStatus = async () => {
  try {
    const response = await fetch('/api/v1/auth/social/status/', {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      }
    });
    
    const data = await response.json();
    
    if (data.requires_password_setup) {
      // Show password setup prompt
      showPasswordSetupModal();
    }
    
    return data;
  } catch (error) {
    console.error('Failed to get social status:', error);
  }
};

Connect Social Provider

const connectProvider = async (provider: string) => {
  try {
    const response = await fetch(`/api/v1/auth/social/connect/${provider}/`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      }
    });
    
    const data = await response.json();
    
    if (data.success) {
      // Redirect to provider auth URL
      window.location.href = data.auth_url;
    }
  } catch (error) {
    console.error('Failed to connect provider:', error);
  }
};

Disconnect Social Provider with Safety Check

const disconnectProvider = async (provider: string) => {
  try {
    const response = await fetch(`/api/v1/auth/social/disconnect/${provider}/`, {
      method: 'DELETE',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      }
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      if (response.status === 400) {
        // Show safety warning with suggestions
        showSafetyWarning(data.error, data.suggestions);
      }
      return;
    }
    
    // Success - update UI
    toast.success(data.message);
    refreshConnectedProviders();
    
  } catch (error) {
    console.error('Failed to disconnect provider:', error);
  }
};

React Component Example

import { useState, useEffect } from 'react';

interface SocialProvider {
  provider: string;
  provider_name: string;
  can_disconnect: boolean;
  disconnect_reason?: string;
}

const SocialProviderManager: React.FC = () => {
  const [connectedProviders, setConnectedProviders] = useState<SocialProvider[]>([]);
  const [hasPasswordAuth, setHasPasswordAuth] = useState(false);
  
  useEffect(() => {
    loadConnectedProviders();
  }, []);
  
  const loadConnectedProviders = async () => {
    const response = await fetch('/api/v1/auth/social/connected/', {
      headers: { 'Authorization': `Bearer ${accessToken}` }
    });
    const data = await response.json();
    
    setConnectedProviders(data.connected_providers);
    setHasPasswordAuth(data.has_password_auth);
  };
  
  const handleDisconnect = async (provider: string) => {
    const response = await fetch(`/api/v1/auth/social/disconnect/${provider}/`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${accessToken}` }
    });
    
    if (!response.ok) {
      const error = await response.json();
      alert(error.error + '\n\nSuggestions:\n' + error.suggestions?.join('\n'));
      return;
    }
    
    loadConnectedProviders(); // Refresh list
  };
  
  return (
    <div className="space-y-4">
      <h3>Connected Social Accounts</h3>
      
      {!hasPasswordAuth && connectedProviders.length === 1 && (
        <div className="bg-yellow-50 p-4 rounded-lg">
          <p className="text-yellow-800">
            ⚠️ Set up a password to safely manage your social connections
          </p>
        </div>
      )}
      
      {connectedProviders.map((provider) => (
        <div key={provider.provider} className="flex items-center justify-between p-4 border rounded">
          <span>{provider.provider_name}</span>
          
          <button
            onClick={() => handleDisconnect(provider.provider)}
            disabled={!provider.can_disconnect}
            className={`px-4 py-2 rounded ${
              provider.can_disconnect 
                ? 'bg-red-600 text-white hover:bg-red-700' 
                : 'bg-gray-300 text-gray-500 cursor-not-allowed'
            }`}
            title={provider.disconnect_reason}
          >
            Disconnect
          </button>
        </div>
      ))}
    </div>
  );
};

Logout

  • POST /api/v1/auth/logout/
  • Returns: { "message": string }

Current User

  • GET /api/v1/auth/user/
  • Returns: Current user profile data
  • Response:
    {
      "id": number,
      "username": string,
      "email": string,
      "display_name": string,
      "is_active": boolean,
      "date_joined": string
    }
    

Password Reset

  • POST /api/v1/auth/password/reset/
  • Body: { "email": string }

Password Change

  • POST /api/v1/auth/password/change/
  • Body: { "old_password": string, "new_password": string }

User Account Management API

User Profile

  • GET /api/v1/accounts/profile/
  • Permissions: Authenticated users only
  • Returns: Complete user profile including account details, preferences, and statistics

Update Account

  • PATCH /api/v1/accounts/profile/account/
  • Permissions: Authenticated users only
  • Body: { "display_name"?: string, "email"?: string }

Update Profile

  • PATCH /api/v1/accounts/profile/update/
  • Permissions: Authenticated users only
  • Body: { "display_name"?: string, "pronouns"?: string, "bio"?: string, "twitter"?: string, "instagram"?: string, "youtube"?: string, "discord"?: string }

Avatar Upload

  • POST /api/v1/accounts/profile/avatar/upload/
  • Permissions: Authenticated users only
  • Content-Type: multipart/form-data
  • Body: FormData with avatar field containing image file (JPEG, PNG, WebP)
  • Returns:
    {
      "success": boolean,
      "message": string,
      "avatar_url": string,
      "avatar_variants": {
        "thumbnail": string, // 64x64
        "avatar": string,    // 200x200
        "large": string      // 400x400
      }
    }
    

⚠️ CRITICAL AUTHENTICATION REQUIREMENT:

  • This endpoint requires authentication via JWT token in Authorization header
  • Common Issue: "Authentication credentials were not provided" (401 error)
  • Root Cause: User not logged in or JWT token not being sent
  • Debug Steps: See docs/avatar-upload-debugging.md for comprehensive troubleshooting guide

Usage Example:

// Ensure user is logged in first
const { user } = useAuth();
if (!user) {
  // Redirect to login
  return;
}

// Upload avatar
const file = event.target.files[0];
const response = await accountApi.uploadAvatar(file);

Test Credentials (for debugging):

  • Username: testuser
  • Password: testpass123
  • Email: test@example.com

Recent Fix (2025-08-29):

  • Fixed file corruption issue where PNG headers were being corrupted during upload
  • Frontend now passes FormData directly instead of extracting and re-wrapping files
  • Backend includes corruption detection and repair mechanism
  • See docs/avatar-upload-debugging.md for complete technical details

Avatar Delete

  • DELETE /api/v1/accounts/profile/avatar/delete/
  • Permissions: Authenticated users only
  • Returns: { "success": boolean, "message": string, "avatar_url": string }

User Preferences

  • GET /api/v1/accounts/preferences/
  • PATCH /api/v1/accounts/preferences/update/
  • Permissions: Authenticated users only

Notification Settings

  • GET /api/v1/accounts/settings/notifications/
  • PATCH /api/v1/accounts/settings/notifications/update/
  • Permissions: Authenticated users only

Privacy Settings

  • GET /api/v1/accounts/settings/privacy/
  • PATCH /api/v1/accounts/settings/privacy/update/
  • Permissions: Authenticated users only

Security Settings

  • GET /api/v1/accounts/settings/security/
  • PATCH /api/v1/accounts/settings/security/update/
  • Permissions: Authenticated users only

User Statistics

  • GET /api/v1/accounts/statistics/
  • Permissions: Authenticated users only
  • Returns: User activity statistics, ride credits, contributions, and achievements

Top Lists

  • GET /api/v1/accounts/top-lists/
  • POST /api/v1/accounts/top-lists/create/
  • PATCH /api/v1/accounts/top-lists/{id}/
  • DELETE /api/v1/accounts/top-lists/{id}/delete/
  • Permissions: Authenticated users only

Notifications

  • GET /api/v1/accounts/notifications/
  • PATCH /api/v1/accounts/notifications/mark-read/
  • Permissions: Authenticated users only

Account Deletion

  • POST /api/v1/accounts/delete-account/request/
  • POST /api/v1/accounts/delete-account/verify/
  • POST /api/v1/accounts/delete-account/cancel/
  • Permissions: Authenticated users only

Statistics API

Global Statistics

  • GET /api/v1/stats/
  • Returns: Global platform statistics
  • GET /api/v1/trending/
  • Query Parameters:
    • content_type: Filter by content type (parks, rides, reviews)
    • time_period: Time period for trending (24h, 7d, 30d)

Latest Reviews

  • GET /api/v1/reviews/latest/
  • Query Parameters:
    • limit: Number of reviews to return
    • park: Filter by park slug
    • ride: Filter by ride slug

Error Handling

All API endpoints return standardized error responses. The system provides enhanced error handling with detailed messages, error codes, and contextual information.

Standard Error Response Format

interface ApiError {
  status: "error";
  error: {
    code: string;
    message: string;
    details?: any;
    request_user?: string;
  };
  data: null;
}

Enhanced Error Response Format

For critical operations like account deletion, the API returns enhanced error responses with additional context:

interface EnhancedApiError {
  status: "error";
  error: {
    code: string;
    message: string;
    error_code: string;
    user_info?: {
      username: string;
      role: string;
      is_superuser: boolean;
      is_staff: boolean;
    };
    help_text?: string;
    details?: any;
    request_user?: string;
  };
  data: null;
}

Error Handling in React Components

Here's how to handle and display enhanced error messages in your NextJS components:

import { useState } from 'react';
import { toast } from 'react-hot-toast';

interface ErrorDisplayProps {
  error: EnhancedApiError | null;
  onDismiss: () => void;
}

const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onDismiss }) => {
  if (!error) return null;

  const { error: errorData } = error;
  
  return (
    <div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
      <div className="flex items-start">
        <div className="flex-shrink-0">
          <ExclamationTriangleIcon className="h-5 w-5 text-red-400" />
        </div>
        <div className="ml-3 flex-1">
          <h3 className="text-sm font-medium text-red-800">
            {errorData.message}
          </h3>
          
          {errorData.error_code && (
            <p className="mt-1 text-xs text-red-600">
              Error Code: {errorData.error_code}
            </p>
          )}
          
          {errorData.user_info && (
            <div className="mt-2 text-xs text-red-600">
              <p>User: {errorData.user_info.username} ({errorData.user_info.role})</p>
              {errorData.user_info.is_superuser && (
                <p className="font-medium">⚠️ Superuser Account</p>
              )}
            </div>
          )}
          
          {errorData.help_text && (
            <div className="mt-3 p-2 bg-red-100 rounded text-xs text-red-700">
              <strong>Help:</strong> {errorData.help_text}
            </div>
          )}
        </div>
        <button
          onClick={onDismiss}
          className="ml-3 flex-shrink-0 text-red-400 hover:text-red-600"
        >
          <XMarkIcon className="h-4 w-4" />
        </button>
      </div>
    </div>
  );
};

// Usage in account deletion component
const AccountDeletionForm: React.FC = () => {
  const [error, setError] = useState<EnhancedApiError | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  const handleDeleteAccount = async () => {
    setIsLoading(true);
    setError(null);
    
    try {
      await api.accounts.requestAccountDeletion();
      toast.success('Account deletion request submitted successfully');
    } catch (err: any) {
      if (err.response?.data) {
        const apiError = err.response.data as EnhancedApiError;
        setError(apiError);
        
        // Also show toast for immediate feedback
        toast.error(apiError.error.message);
        
        // Log security-related errors for monitoring
        if (apiError.error.error_code === 'SUPERUSER_DELETION_BLOCKED') {
          console.warn('Superuser deletion attempt blocked:', {
            user: apiError.error.user_info?.username,
            timestamp: new Date().toISOString()
          });
        }
      } else {
        setError({
          status: "error",
          error: {
            code: "UNKNOWN_ERROR",
            message: "An unexpected error occurred",
            error_code: "UNKNOWN_ERROR"
          },
          data: null
        });
      }
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="max-w-md mx-auto">
      <ErrorDisplay 
        error={error} 
        onDismiss={() => setError(null)} 
      />
      
      <button
        onClick={handleDeleteAccount}
        disabled={isLoading}
        className="w-full bg-red-600 text-white py-2 px-4 rounded hover:bg-red-700 disabled:opacity-50"
      >
        {isLoading ? 'Processing...' : 'Delete Account'}
      </button>
    </div>
  );
};

Toast Notifications for Errors

For immediate user feedback, combine detailed error displays with toast notifications:

import { toast } from 'react-hot-toast';

const handleApiError = (error: EnhancedApiError) => {
  const { error: errorData } = error;
  
  // Show immediate toast
  toast.error(errorData.message, {
    duration: 5000,
    position: 'top-right',
  });
  
  // For critical errors, show additional context
  if (errorData.error_code === 'SUPERUSER_DELETION_BLOCKED') {
    toast.error('Superuser accounts cannot be deleted for security reasons', {
      duration: 8000,
      icon: '🔒',
    });
  }
  
  if (errorData.error_code === 'ADMIN_DELETION_BLOCKED') {
    toast.error('Admin accounts with staff privileges cannot be deleted', {
      duration: 8000,
      icon: '⚠️',
    });
  }
};

Error Boundary for Global Error Handling

Create an error boundary to catch and display API errors globally:

import React from 'react';

interface ErrorBoundaryState {
  hasError: boolean;
  error: EnhancedApiError | null;
}

class ApiErrorBoundary extends React.Component<
  React.PropsWithChildren<{}>,
  ErrorBoundaryState
> {
  constructor(props: React.PropsWithChildren<{}>) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: any): ErrorBoundaryState {
    if (error.response?.data?.status === 'error') {
      return {
        hasError: true,
        error: error.response.data as EnhancedApiError
      };
    }
    return { hasError: true, error: null };
  }

  render() {
    if (this.state.hasError && this.state.error) {
      return (
        <ErrorDisplay 
          error={this.state.error}
          onDismiss={() => this.setState({ hasError: false, error: null })}
        />
      );
    }

    return this.props.children;
  }
}

Common Error Codes

The system uses specific error codes for different scenarios:

  • NOT_AUTHENTICATED: User not logged in
  • PERMISSION_DENIED: Insufficient permissions
  • NOT_FOUND: Resource not found
  • VALIDATION_ERROR: Invalid request data
  • RATE_LIMITED: Too many requests
  • SUPERUSER_DELETION_BLOCKED: Superuser account deletion attempt
  • ADMIN_DELETION_BLOCKED: Admin account with staff privileges deletion attempt
  • ACCOUNT_DELETION_FAILED: General account deletion failure
  • SECURITY_VIOLATION: Security policy violation detected

Error Logging and Monitoring

For production applications, implement error logging:

const logError = (error: EnhancedApiError, context: string) => {
  // Log to your monitoring service (e.g., Sentry, LogRocket)
  console.error(`API Error in ${context}:`, {
    code: error.error.code,
    message: error.error.message,
    errorCode: error.error.error_code,
    userInfo: error.error.user_info,
    timestamp: new Date().toISOString(),
    context
  });
  
  // Send to analytics if it's a security-related error
  if (error.error.error_code?.includes('DELETION_BLOCKED')) {
    // Track security events
    analytics.track('Security Event', {
      event: 'Account Deletion Blocked',
      errorCode: error.error.error_code,
      user: error.error.user_info?.username
    });
  }
};

Pagination

List endpoints use cursor-based pagination:

interface PaginatedResponse<T> {
  status: "success";
  data: {
    results: T[];
    count: number;
    next: string | null;
    previous: string | null;
  };
  error: null;
}

Rate Limiting

API endpoints are rate limited based on user role:

  • Anonymous users: 100 requests/hour
  • Authenticated users: 1000 requests/hour
  • Moderators: 5000 requests/hour
  • Admins: 10000 requests/hour

WebSocket Connections

Real-time updates are available for:

  • Moderation queue updates
  • New reports and actions
  • Bulk operation progress
  • Live statistics updates

Connect to: ws://localhost:8000/ws/moderation/ (requires authentication)