# ThrillWiki Frontend Connection Guide

A comprehensive guide for connecting any frontend framework to the ThrillWiki Django REST API backend.

## Table of Contents

1. [Overview](#overview)
2. [API Architecture](#api-architecture)
3. [Authentication System](#authentication-system)
4. [Core API Endpoints](#core-api-endpoints)
5. [Frontend Framework Examples](#frontend-framework-examples)
6. [Dark/Light Mode Requirements](#darklight-mode-requirements)
7. [Error Handling](#error-handling)
8. [Best Practices](#best-practices)
9. [Troubleshooting](#troubleshooting)

## Overview

ThrillWiki uses a modern Django REST Framework backend with a centralized API architecture. All API endpoints are organized under `/api/v1/` with domain-specific modules for parks, rides, authentication, and more.

### Key Features
- **Centralized API Structure** - All endpoints under `/api/v1/`
- **Token-based Authentication** - Using Django REST Framework tokens
- **Comprehensive Serialization** - Well-structured JSON responses
- **CORS Support** - Configured for frontend integration
- **OpenAPI Documentation** - Auto-generated API docs at `/api/docs/`

### Base Configuration
```javascript
const API_BASE_URL = 'http://localhost:8000/api/v1'
```

## API Architecture

### Centralized Structure
All API endpoints follow this pattern:
```
/api/v1/{domain}/{endpoint}/
```

### Domain Organization
- **Authentication**: `/api/v1/auth/`
- **Parks**: `/api/v1/parks/`
- **Rides**: `/api/v1/rides/`
- **Accounts**: `/api/v1/accounts/`
- **Health**: `/api/v1/health/`

### URL Routing
The main API router is located at `backend/apps/api/v1/urls.py`:

```python
# Main API v1 URLs
urlpatterns = [
    path("auth/", include("apps.api.v1.auth.urls")),
    path("health/", include("apps.api.v1.health.urls")),
    path("trending/", include("apps.api.v1.trending.urls")),
    path("parks/", include("apps.api.v1.parks.urls")),
    path("rides/", include("apps.api.v1.rides.urls")),
    path("accounts/", include("apps.api.v1.accounts.urls")),
]
```

## Authentication System

### Token-Based Authentication

ThrillWiki uses Django REST Framework's token authentication system.

#### Authentication Flow

1. **Login/Signup** - Obtain authentication token
2. **Store Token** - Save token securely (localStorage, sessionStorage, or cookies)
3. **Include Token** - Add token to all authenticated requests
4. **Handle Expiration** - Refresh or re-authenticate when token expires

#### Authentication Endpoints

| Endpoint | Method | Purpose | Authentication Required |
|----------|--------|---------|------------------------|
| `/api/v1/auth/login/` | POST | User login | No |
| `/api/v1/auth/signup/` | POST | User registration | No |
| `/api/v1/auth/logout/` | POST | User logout | Yes |
| `/api/v1/auth/current-user/` | GET | Get current user info | Yes |
| `/api/v1/auth/password-reset/` | POST | Request password reset | No |
| `/api/v1/auth/password-change/` | POST | Change password | Yes |
| `/api/v1/auth/status/` | POST | Check auth status | No |
| `/api/v1/auth/social-providers/` | GET | Get social auth providers | No |

#### Request/Response Examples

**Login Request:**
```json
{
  "username": "thrillseeker",
  "password": "securepassword123"
}
```

**Login Response:**
```json
{
  "token": "abc123def456ghi789",
  "user": {
    "id": 1,
    "username": "thrillseeker",
    "email": "user@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "is_active": true,
    "date_joined": "2024-01-01T00:00:00Z"
  },
  "message": "Login successful"
}
```

**Signup Request:**
```json
{
  "username": "newuser",
  "email": "newuser@example.com",
  "password": "securepassword123",
  "password_confirm": "securepassword123",
  "first_name": "Jane",
  "last_name": "Smith"
}
```

## Core API Endpoints

### Parks API (`/api/v1/parks/`)

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/v1/parks/` | GET | List all parks |
| `/api/v1/parks/` | POST | Create new park |
| `/api/v1/parks/{id}/` | GET | Get park details |
| `/api/v1/parks/{id}/` | PUT/PATCH | Update park |
| `/api/v1/parks/{id}/` | DELETE | Delete park |
| `/api/v1/parks/search/` | GET | Search parks |
| `/api/v1/parks/filter/` | GET | Filter parks |
| `/api/v1/parks/{id}/photos/` | GET/POST | Park photos |

### Rides API (`/api/v1/rides/`)

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/v1/rides/` | GET | List all rides |
| `/api/v1/rides/` | POST | Create new ride |
| `/api/v1/rides/{id}/` | GET | Get ride details |
| `/api/v1/rides/{id}/` | PUT/PATCH | Update ride |
| `/api/v1/rides/{id}/` | DELETE | Delete ride |
| `/api/v1/rides/search/` | GET | Search rides |
| `/api/v1/rides/filter/` | GET | Filter rides |
| `/api/v1/rides/{id}/photos/` | GET/POST | Ride photos |

### Health Check (`/api/v1/health/`)

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/v1/health/` | GET | Basic health check |
| `/api/v1/health/detailed/` | GET | Detailed system status |

## Frontend Framework Examples

### Vanilla JavaScript

```javascript
class ThrillWikiAPI {
  constructor(baseURL = 'http://localhost:8000/api/v1') {
    this.baseURL = baseURL;
    this.token = localStorage.getItem('thrillwiki_token');
  }

  // Set authentication token
  setToken(token) {
    this.token = token;
    localStorage.setItem('thrillwiki_token', token);
  }

  // Remove authentication token
  clearToken() {
    this.token = null;
    localStorage.removeItem('thrillwiki_token');
  }

  // Make authenticated request
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const headers = {
      'Content-Type': 'application/json',
      ...options.headers
    };

    if (this.token) {
      headers['Authorization'] = `Token ${this.token}`;
    }

    const response = await fetch(url, {
      ...options,
      headers
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }

  // Authentication methods
  async login(username, password) {
    const response = await this.request('/auth/login/', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    });
    
    if (response.token) {
      this.setToken(response.token);
    }
    
    return response;
  }

  async signup(userData) {
    const response = await this.request('/auth/signup/', {
      method: 'POST',
      body: JSON.stringify(userData)
    });
    
    if (response.token) {
      this.setToken(response.token);
    }
    
    return response;
  }

  async logout() {
    try {
      await this.request('/auth/logout/', { method: 'POST' });
    } finally {
      this.clearToken();
    }
  }

  async getCurrentUser() {
    return this.request('/auth/current-user/');
  }

  // Parks methods
  async getParks(params = {}) {
    const queryString = new URLSearchParams(params).toString();
    return this.request(`/parks/${queryString ? '?' + queryString : ''}`);
  }

  async getPark(id) {
    return this.request(`/parks/${id}/`);
  }

  async createPark(parkData) {
    return this.request('/parks/', {
      method: 'POST',
      body: JSON.stringify(parkData)
    });
  }

  // Rides methods
  async getRides(params = {}) {
    const queryString = new URLSearchParams(params).toString();
    return this.request(`/rides/${queryString ? '?' + queryString : ''}`);
  }

  async getRide(id) {
    return this.request(`/rides/${id}/`);
  }

  async createRide(rideData) {
    return this.request('/rides/', {
      method: 'POST',
      body: JSON.stringify(rideData)
    });
  }
}

// Usage example
const api = new ThrillWikiAPI();

// Login
api.login('username', 'password')
  .then(response => {
    console.log('Logged in:', response.user);
    return api.getParks();
  })
  .then(parks => {
    console.log('Parks:', parks);
  })
  .catch(error => {
    console.error('Error:', error);
  });
```

### React with Axios

```jsx
import axios from 'axios';
import { createContext, useContext, useState, useEffect } from 'react';

// API Configuration
const API_BASE_URL = 'http://localhost:8000/api/v1';

const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor to add auth token
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('thrillwiki_token');
  if (token) {
    config.headers.Authorization = `Token ${token}`;
  }
  return config;
});

// Response interceptor for error handling
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('thrillwiki_token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// Auth Context
const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem('thrillwiki_token');
    if (token) {
      getCurrentUser();
    } else {
      setLoading(false);
    }
  }, []);

  const getCurrentUser = async () => {
    try {
      const response = await api.get('/auth/current-user/');
      setUser(response.data);
    } catch (error) {
      localStorage.removeItem('thrillwiki_token');
    } finally {
      setLoading(false);
    }
  };

  const login = async (username, password) => {
    const response = await api.post('/auth/login/', { username, password });
    localStorage.setItem('thrillwiki_token', response.data.token);
    setUser(response.data.user);
    return response.data;
  };

  const signup = async (userData) => {
    const response = await api.post('/auth/signup/', userData);
    localStorage.setItem('thrillwiki_token', response.data.token);
    setUser(response.data.user);
    return response.data;
  };

  const logout = async () => {
    try {
      await api.post('/auth/logout/');
    } finally {
      localStorage.removeItem('thrillwiki_token');
      setUser(null);
    }
  };

  return (
    <AuthContext.Provider value={{ user, login, signup, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

// API Service
export const apiService = {
  // Parks
  getParks: (params) => api.get('/parks/', { params }),
  getPark: (id) => api.get(`/parks/${id}/`),
  createPark: (data) => api.post('/parks/', data),
  updatePark: (id, data) => api.patch(`/parks/${id}/`, data),
  deletePark: (id) => api.delete(`/parks/${id}/`),

  // Rides
  getRides: (params) => api.get('/rides/', { params }),
  getRide: (id) => api.get(`/rides/${id}/`),
  createRide: (data) => api.post('/rides/', data),
  updateRide: (id, data) => api.patch(`/rides/${id}/`, data),
  deleteRide: (id) => api.delete(`/rides/${id}/`),
};

// Component Example
const ParksList = () => {
  const [parks, setParks] = useState([]);
  const [loading, setLoading] = useState(true);
  const { user } = useAuth();

  useEffect(() => {
    const fetchParks = async () => {
      try {
        const response = await apiService.getParks();
        setParks(response.data.results || response.data);
      } catch (error) {
        console.error('Error fetching parks:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchParks();
  }, []);

  if (loading) return <div>Loading parks...</div>;

  return (
    <div>
      <h2>Theme Parks</h2>
      {parks.map(park => (
        <div key={park.id} className="park-card">
          <h3>{park.name}</h3>
          <p>{park.description}</p>
          <p>Location: {park.location}</p>
        </div>
      ))}
    </div>
  );
};
```

### Vue.js 3 with Composition API

```vue
<template>
  <div>
    <h2>Theme Parks</h2>
    <div v-if="loading">Loading parks...</div>
    <div v-else>
      <div v-for="park in parks" :key="park.id" class="park-card">
        <h3>{{ park.name }}</h3>
        <p>{{ park.description }}</p>
        <p>Location: {{ park.location }}</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useApi } from '@/composables/useApi'

const { apiService } = useApi()
const parks = ref([])
const loading = ref(true)

onMounted(async () => {
  try {
    const response = await apiService.getParks()
    parks.value = response.data.results || response.data
  } catch (error) {
    console.error('Error fetching parks:', error)
  } finally {
    loading.value = false
  }
})
</script>
```

**Vue Composable (`composables/useApi.js`):**

```javascript
import axios from 'axios'
import { ref, computed } from 'vue'

const API_BASE_URL = 'http://localhost:8000/api/v1'

const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
})

// Global state
const user = ref(null)
const token = ref(localStorage.getItem('thrillwiki_token'))

// Request interceptor
api.interceptors.request.use((config) => {
  if (token.value) {
    config.headers.Authorization = `Token ${token.value}`
  }
  return config
})

// Response interceptor
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      token.value = null
      user.value = null
      localStorage.removeItem('thrillwiki_token')
    }
    return Promise.reject(error)
  }
)

export function useApi() {
  const isAuthenticated = computed(() => !!token.value)

  const setToken = (newToken) => {
    token.value = newToken
    localStorage.setItem('thrillwiki_token', newToken)
  }

  const clearToken = () => {
    token.value = null
    user.value = null
    localStorage.removeItem('thrillwiki_token')
  }

  const login = async (username, password) => {
    const response = await api.post('/auth/login/', { username, password })
    setToken(response.data.token)
    user.value = response.data.user
    return response.data
  }

  const signup = async (userData) => {
    const response = await api.post('/auth/signup/', userData)
    setToken(response.data.token)
    user.value = response.data.user
    return response.data
  }

  const logout = async () => {
    try {
      await api.post('/auth/logout/')
    } finally {
      clearToken()
    }
  }

  const getCurrentUser = async () => {
    const response = await api.get('/auth/current-user/')
    user.value = response.data
    return response.data
  }

  const apiService = {
    // Parks
    getParks: (params) => api.get('/parks/', { params }),
    getPark: (id) => api.get(`/parks/${id}/`),
    createPark: (data) => api.post('/parks/', data),
    updatePark: (id, data) => api.patch(`/parks/${id}/`, data),
    deletePark: (id) => api.delete(`/parks/${id}/`),

    // Rides
    getRides: (params) => api.get('/rides/', { params }),
    getRide: (id) => api.get(`/rides/${id}/`),
    createRide: (data) => api.post('/rides/', data),
    updateRide: (id, data) => api.patch(`/rides/${id}/`, data),
    deleteRide: (id) => api.delete(`/rides/${id}/`),
  }

  return {
    user: readonly(user),
    isAuthenticated,
    login,
    signup,
    logout,
    getCurrentUser,
    apiService
  }
}
```

### Angular with HttpClient

```typescript
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';

export interface User {
  id: number;
  username: string;
  email: string;
  first_name: string;
  last_name: string;
  is_active: boolean;
  date_joined: string;
}

export interface LoginResponse {
  token: string;
  user: User;
  message: string;
}

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  private baseURL = 'http://localhost:8000/api/v1';
  private tokenSubject = new BehaviorSubject<string | null>(
    localStorage.getItem('thrillwiki_token')
  );
  private userSubject = new BehaviorSubject<User | null>(null);

  public token$ = this.tokenSubject.asObservable();
  public user$ = this.userSubject.asObservable();

  constructor(private http: HttpClient) {
    // Initialize user if token exists
    if (this.tokenSubject.value) {
      this.getCurrentUser().subscribe();
    }
  }

  private getHeaders(): HttpHeaders {
    const token = this.tokenSubject.value;
    let headers = new HttpHeaders({
      'Content-Type': 'application/json'
    });

    if (token) {
      headers = headers.set('Authorization', `Token ${token}`);
    }

    return headers;
  }

  // Authentication methods
  login(username: string, password: string): Observable<LoginResponse> {
    return this.http.post<LoginResponse>(`${this.baseURL}/auth/login/`, {
      username,
      password
    }).pipe(
      tap(response => {
        localStorage.setItem('thrillwiki_token', response.token);
        this.tokenSubject.next(response.token);
        this.userSubject.next(response.user);
      })
    );
  }

  signup(userData: any): Observable<LoginResponse> {
    return this.http.post<LoginResponse>(`${this.baseURL}/auth/signup/`, userData)
      .pipe(
        tap(response => {
          localStorage.setItem('thrillwiki_token', response.token);
          this.tokenSubject.next(response.token);
          this.userSubject.next(response.user);
        })
      );
  }

  logout(): Observable<any> {
    return this.http.post(`${this.baseURL}/auth/logout/`, {}, {
      headers: this.getHeaders()
    }).pipe(
      tap(() => {
        localStorage.removeItem('thrillwiki_token');
        this.tokenSubject.next(null);
        this.userSubject.next(null);
      })
    );
  }

  getCurrentUser(): Observable<User> {
    return this.http.get<User>(`${this.baseURL}/auth/current-user/`, {
      headers: this.getHeaders()
    }).pipe(
      tap(user => this.userSubject.next(user))
    );
  }

  // Parks methods
  getParks(params?: any): Observable<any> {
    let httpParams = new HttpParams();
    if (params) {
      Object.keys(params).forEach(key => {
        httpParams = httpParams.set(key, params[key]);
      });
    }

    return this.http.get(`${this.baseURL}/parks/`, {
      headers: this.getHeaders(),
      params: httpParams
    });
  }

  getPark(id: number): Observable<any> {
    return this.http.get(`${this.baseURL}/parks/${id}/`, {
      headers: this.getHeaders()
    });
  }

  createPark(parkData: any): Observable<any> {
    return this.http.post(`${this.baseURL}/parks/`, parkData, {
      headers: this.getHeaders()
    });
  }

  // Rides methods
  getRides(params?: any): Observable<any> {
    let httpParams = new HttpParams();
    if (params) {
      Object.keys(params).forEach(key => {
        httpParams = httpParams.set(key, params[key]);
      });
    }

    return this.http.get(`${this.baseURL}/rides/`, {
      headers: this.getHeaders(),
      params: httpParams
    });
  }

  getRide(id: number): Observable<any> {
    return this.http.get(`${this.baseURL}/rides/${id}/`, {
      headers: this.getHeaders()
    });
  }

  createRide(rideData: any): Observable<any> {
    return this.http.post(`${this.baseURL}/rides/`, rideData, {
      headers: this.getHeaders()
    });
  }
}
```

**Component Example:**

```typescript
// parks-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ApiService } from '../services/api.service';

@Component({
  selector: 'app-parks-list',
  template: `
    <div>
      <h2>Theme Parks</h2>
      <div *ngIf="loading">Loading parks...</div>
      <div *ngIf="!loading">
        <div *ngFor="let park of parks" class="park-card">
          <h3>{{ park.name }}</h3>
          <p>{{ park.description }}</p>
          <p>Location: {{ park.location }}</p>
        </div>
      </div>
    </div>
  `
})
export class ParksListComponent implements OnInit {
  parks: any[] = [];
  loading = true;

  constructor(private apiService: ApiService) {}

  ngOnInit() {
    this.apiService.getParks().subscribe({
      next: (response) => {
        this.parks = response.results || response;
        this.loading = false;
      },
      error: (error) => {
        console.error('Error fetching parks:', error);
        this.loading = false;
      }
    });
  }
}
```

## Dark/Light Mode Requirements

ThrillWiki requires comprehensive dark/light mode support across all frontend implementations.

### CSS Variables Approach

```css
:root {
  /* Light mode colors */
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --text-primary: #212529;
  --text-secondary: #6c757d;
  --border-color: #dee2e6;
  --accent-color: #007bff;
}

[data-theme="dark"] {
  /* Dark mode colors */
  --bg-primary: #1a1a1a;
  --bg-secondary: #2d2d2d;
  --text-primary: #ffffff;
  --text-secondary: #b0b0b0;
  --border-color: #404040;
  --accent-color: #4dabf7;
}

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
  transition: background-color 0.3s ease, color 0.3s ease;
}
```

### Theme Toggle Implementation

**Vanilla JavaScript:**
```javascript
class ThemeManager {
  constructor() {
    this.theme = localStorage.getItem('theme') || 'light';
    this.applyTheme();
  }

  applyTheme() {
    document.documentElement.setAttribute('data-theme', this.theme);
    localStorage.setItem('theme', this.theme);
  }

  toggle() {
    this.theme = this.theme === 'light' ? 'dark' : 'light';
    this.applyTheme();
  }

  setTheme(theme) {
    this.theme = theme;
    this.applyTheme();
  }
}

const themeManager = new ThemeManager();

// Theme toggle button
document.getElementById('theme-toggle').addEventListener('click', () => {
  themeManager.toggle();
});
```

**React Hook:**
```jsx
import { useState, useEffect } from 'react';

export const useTheme = () => {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }, [theme]);

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return { theme, toggleTheme, setTheme };
};

// Component usage
const ThemeToggle = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme} className="theme-toggle">
      {theme === 'light' ? '🌙' : '☀️'}
    </button>
  );
};
```

**Vue Composable:**
```javascript
import { ref, watch } from 'vue';

export function useTheme() {
  const theme = ref(localStorage.getItem('theme') || 'light');

  const applyTheme = (newTheme) => {
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
  };

  const toggleTheme = () => {
    theme.value = theme.value === 'light' ? 'dark' : 'light';
  };

  const setTheme = (newTheme) => {
    theme.value = newTheme;
  };

  watch(theme, applyTheme, { immediate: true });

  return {
    theme: readonly(theme),
    toggleTheme,
    setTheme
  };
}
```

### Tailwind CSS Integration

If using Tailwind CSS, configure dark mode in `tailwind.config.js`:

```javascript
module.exports = {
  darkMode: ['class', '[data-theme="dark"]'],
  theme: {
    extend: {
      colors: {
        primary: {
          light: '#ffffff',
          dark: '#1a1a1a'
        },
        secondary: {
          light: '#f8f9fa',
          dark: '#2d2d2d'
        }
      }
    }
  }
}
```

Usage in components:
```html
<div class="bg-primary-light dark:bg-primary-dark text-gray-900 dark:text-white">
  Content that adapts to theme
</div>
```

## Error Handling

### HTTP Status Codes

The API returns standard HTTP status codes:

- **200 OK** - Successful GET, PUT, PATCH
- **201 Created** - Successful POST
- **204 No Content** - Successful DELETE
- **400 Bad Request** - Invalid request data
- **401 Unauthorized** - Authentication required
- **403 Forbidden** - Permission denied
- **404 Not Found** - Resource not found
- **500 Internal Server Error** - Server error

### Error Response Format

```json
{
  "error": "Error message",
  "details": {
    "field_name": ["Field-specific error message"]
  }
}
```

### Error Handling Examples

**JavaScript:**
```javascript
async function handleApiCall(apiFunction) {
  try {
    const response = await apiFunction();
    return response;
  } catch (error) {
    if (error.response) {
      // Server responded with error status
      const { status, data } = error.response;
      
      switch (status) {
        case 401:
          // Handle authentication error
          redirectToLogin();
          break;
        case 403:
          // Handle permission error
          showErrorMessage('You do not have permission to perform this action');
          break;
        case 404:
          // Handle not found
          showErrorMessage('Resource not found');
          break;
        case 500:
          // Handle server error
          showErrorMessage('Server error. Please try again later.');
          break;
        default:
          // Handle other errors
          showErrorMessage(data.error || 'An error occurred');
      }
    } else if (error.request) {
      // Network error
      showErrorMessage('Network error. Please check your connection.');
    } else {
      // Other error
      showErrorMessage('An unexpected error occurred');
    }
    
    throw error;
  }
}
```

**React Error Boundary:**
```jsx
class ApiErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('API Error:', error, errorInfo);
  }

  render() {
    if
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <p>Please try refreshing the page or contact support if the problem persists.</p>
        </div>
      );
    }

    return this.props.children;
  }
}
```

## Best Practices

### Security Considerations

1. **Token Storage**
   - Use `httpOnly` cookies for production when possible
   - Avoid storing tokens in localStorage for sensitive applications
   - Implement token refresh mechanisms

2. **HTTPS Only**
   - Always use HTTPS in production
   - Configure secure cookie flags

3. **Input Validation**
   - Validate all user inputs on the frontend
   - Sanitize data before sending to API

4. **CORS Configuration**
   - Configure CORS properly in Django settings
   - Restrict allowed origins in production

### Performance Optimization

1. **Request Caching**
   ```javascript
   // Simple cache implementation
   class ApiCache {
     constructor(ttl = 300000) { // 5 minutes default
       this.cache = new Map();
       this.ttl = ttl;
     }

     get(key) {
       const item = this.cache.get(key);
       if (!item) return null;
       
       if (Date.now() > item.expiry) {
         this.cache.delete(key);
         return null;
       }
       
       return item.data;
     }

     set(key, data) {
       this.cache.set(key, {
         data,
         expiry: Date.now() + this.ttl
       });
     }

     clear() {
       this.cache.clear();
     }
   }

   const apiCache = new ApiCache();

   // Usage in API calls
   async function getCachedParks() {
     const cacheKey = 'parks_list';
     let parks = apiCache.get(cacheKey);
     
     if (!parks) {
       const response = await api.get('/parks/');
       parks = response.data;
       apiCache.set(cacheKey, parks);
     }
     
     return parks;
   }
   ```

2. **Request Debouncing**
   ```javascript
   function debounce(func, wait) {
     let timeout;
     return function executedFunction(...args) {
       const later = () => {
         clearTimeout(timeout);
         func(...args);
       };
       clearTimeout(timeout);
       timeout = setTimeout(later, wait);
     };
   }

   // Usage for search
   const debouncedSearch = debounce(async (query) => {
     if (query.length > 2) {
       const results = await api.get('/parks/search/', { params: { q: query } });
       setSearchResults(results.data);
     }
   }, 300);
   ```

3. **Pagination Handling**
   ```javascript
   class PaginatedAPI {
     constructor(baseURL) {
       this.baseURL = baseURL;
     }

     async getAllPages(endpoint, params = {}) {
       let allResults = [];
       let nextUrl = `${this.baseURL}${endpoint}`;
       
       while (nextUrl) {
         const response = await fetch(nextUrl, {
           headers: this.getHeaders(),
           ...params
         });
         const data = await response.json();
         
         allResults = allResults.concat(data.results || []);
         nextUrl = data.next;
       }
       
       return allResults;
     }

     async getPage(endpoint, page = 1, pageSize = 20) {
       const response = await fetch(`${this.baseURL}${endpoint}?page=${page}&page_size=${pageSize}`, {
         headers: this.getHeaders()
       });
       return response.json();
     }
   }
   ```

### Code Organization

1. **Service Layer Pattern**
   ```javascript
   // services/api.js
   export class APIService {
     constructor(baseURL, authService) {
       this.baseURL = baseURL;
       this.authService = authService;
     }

     async request(endpoint, options = {}) {
       // Common request logic
     }
   }

   // services/parksService.js
   export class ParksService extends APIService {
     async getParks(filters = {}) {
       return this.request('/parks/', { params: filters });
     }

     async getPark(id) {
       return this.request(`/parks/${id}/`);
     }
   }

   // services/index.js
   export { APIService } from './api';
   export { ParksService } from './parksService';
   export { RidesService } from './ridesService';
   export { AuthService } from './authService';
   ```

2. **Environment Configuration**
   ```javascript
   // config/api.js
   const config = {
     development: {
       API_BASE_URL: 'http://localhost:8000/api/v1',
       ENABLE_LOGGING: true,
       CACHE_TTL: 60000 // 1 minute
     },
     production: {
       API_BASE_URL: 'https://api.thrillwiki.com/api/v1',
       ENABLE_LOGGING: false,
       CACHE_TTL: 300000 // 5 minutes
     }
   };

   export default config[process.env.NODE_ENV || 'development'];
   ```

## Troubleshooting

### Common Issues

#### 1. CORS Errors
**Problem:** Browser blocks requests due to CORS policy

**Solution:**
- Ensure Django CORS settings are configured:
  ```python
  # settings.py
  CORS_ALLOWED_ORIGINS = [
      "http://localhost:3000",
      "http://127.0.0.1:3000",
      "http://localhost:5173",  # Vite default
  ]
  
  CORS_ALLOW_CREDENTIALS = True
  ```

#### 2. Authentication Token Issues
**Problem:** 401 Unauthorized errors

**Solutions:**
- Check token format: `Authorization: Token abc123def456`
- Verify token is not expired
- Ensure token is included in requests:
  ```javascript
  // Check if token exists
  const token = localStorage.getItem('thrillwiki_token');
  if (!token) {
    // Redirect to login
    window.location.href = '/login';
    return;
  }
  ```

#### 3. Network Connectivity
**Problem:** Network errors or timeouts

**Solutions:**
- Implement retry logic:
  ```javascript
  async function retryRequest(fn, retries = 3, delay = 1000) {
    try {
      return await fn();
    } catch (error) {
      if (retries > 0 && error.code === 'NETWORK_ERROR') {
        await new Promise(resolve => setTimeout(resolve, delay));
        return retryRequest(fn, retries - 1, delay * 2);
      }
      throw error;
    }
  }
  ```

#### 4. Data Serialization Issues
**Problem:** Unexpected data formats or missing fields

**Solutions:**
- Always validate API responses:
  ```javascript
  function validateParkData(park) {
    const required = ['id', 'name', 'location'];
    const missing = required.filter(field => !park[field]);
    
    if (missing.length > 0) {
      throw new Error(`Missing required fields: ${missing.join(', ')}`);
    }
    
    return park;
  }
  ```

#### 5. Proxy Configuration Issues (Vite/Webpack)
**Problem:** API calls fail in development due to proxy misconfiguration

**Vite Solution:**
```javascript
// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
        secure: false,
      }
    }
  }
});
```

**Webpack Solution:**
```javascript
// webpack.config.js or next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8000/api/:path*',
      },
    ];
  },
};
```

### Debugging Tools

#### 1. API Request Logging
```javascript
class APILogger {
  static log(method, url, data, response) {
    if (process.env.NODE_ENV === 'development') {
      console.group(`🌐 API ${method.toUpperCase()} ${url}`);
      if (data) console.log('📤 Request:', data);
      console.log('📥 Response:', response);
      console.groupEnd();
    }
  }

  static error(method, url, error) {
    console.group(`❌ API ${method.toUpperCase()} ${url} FAILED`);
    console.error('Error:', error);
    console.groupEnd();
  }
}

// Usage in API service
async request(endpoint, options = {}) {
  try {
    const response = await fetch(url, options);
    const data = await response.json();
    
    APILogger.log(options.method || 'GET', endpoint, options.body, data);
    return data;
  } catch (error) {
    APILogger.error(options.method || 'GET', endpoint, error);
    throw error;
  }
}
```

#### 2. Network Monitoring
```javascript
// Monitor network status
class NetworkMonitor {
  constructor() {
    this.isOnline = navigator.onLine;
    this.listeners = [];
    
    window.addEventListener('online', () => this.setOnline(true));
    window.addEventListener('offline', () => this.setOnline(false));
  }

  setOnline(status) {
    this.isOnline = status;
    this.listeners.forEach(listener => listener(status));
  }

  onStatusChange(callback) {
    this.listeners.push(callback);
  }
}

const networkMonitor = new NetworkMonitor();
networkMonitor.onStatusChange((isOnline) => {
  if (isOnline) {
    console.log('🟢 Back online - retrying failed requests');
    // Retry failed requests
  } else {
    console.log('🔴 Gone offline - queuing requests');
    // Queue requests for later
  }
});
```

### Testing API Integration

#### Unit Testing Example (Jest)
```javascript
// __tests__/api.test.js
import { APIService } from '../services/api';

// Mock fetch
global.fetch = jest.fn();

describe('APIService', () => {
  let apiService;

  beforeEach(() => {
    apiService = new APIService('http://localhost:8000/api/v1');
    fetch.mockClear();
  });

  test('should fetch parks successfully', async () => {
    const mockParks = [
      { id: 1, name: 'Test Park', location: 'Test Location' }
    ];

    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => ({ results: mockParks })
    });

    const parks = await apiService.getParks();
    
    expect(fetch).toHaveBeenCalledWith(
      'http://localhost:8000/api/v1/parks/',
      expect.objectContaining({
        headers: expect.objectContaining({
          'Content-Type': 'application/json'
        })
      })
    );
    
    expect(parks.results).toEqual(mockParks);
  });

  test('should handle authentication errors', async () => {
    fetch.mockResolvedValueOnce({
      ok: false,
      status: 401,
      json: async () => ({ error: 'Unauthorized' })
    });

    await expect(apiService.getParks()).rejects.toThrow('Unauthorized');
  });
});
```

### Performance Monitoring

```javascript
// Performance monitoring utility
class PerformanceMonitor {
  static startTimer(label) {
    performance.mark(`${label}-start`);
  }

  static endTimer(label) {
    performance.mark(`${label}-end`);
    performance.measure(label, `${label}-start`, `${label}-end`);
    
    const measure = performance.getEntriesByName(label)[0];
    console.log(`⏱️ ${label}: ${measure.duration.toFixed(2)}ms`);
    
    // Clean up
    performance.clearMarks(`${label}-start`);
    performance.clearMarks(`${label}-end`);
    performance.clearMeasures(label);
  }
}

// Usage
PerformanceMonitor.startTimer('api-parks-fetch');
const parks = await api.getParks();
PerformanceMonitor.endTimer('api-parks-fetch');
```

## Additional Resources

### API Documentation
- **Swagger/OpenAPI Docs**: `http://localhost:8000/api/docs/`
- **ReDoc Documentation**: `http://localhost:8000/api/redoc/`
- **API Schema**: `http://localhost:8000/api/schema/`

### Development Tools
- **Django Admin**: `http://localhost:8000/admin/`
- **Health Check**: `http://localhost:8000/api/v1/health/`
- **Environment Settings**: `http://localhost:8000/env-settings/`

### Example Projects
Check the `frontend/` directory for a complete Vue.js implementation example that demonstrates all the patterns described in this guide.

### Community Resources
- **Django REST Framework Documentation**: https://www.django-rest-framework.org/
- **Vue.js Documentation**: https://vuejs.org/
- **React Documentation**: https://react.dev/
- **Angular Documentation**: https://angular.io/

---

This guide provides a comprehensive foundation for connecting any frontend framework to the ThrillWiki Django backend. For specific implementation questions or issues not covered here, please refer to the project's issue tracker or documentation.