> ## Documentation Index
> Fetch the complete documentation index at: https://docs.billbora.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend integration

# Frontend Integration Guide

This comprehensive guide covers integrating the Billbora API with modern frontend applications, including React, Vue, and vanilla JavaScript implementations.

## Overview

Billbora provides multiple integration options for frontend applications:

<CardGroup cols={2}>
  <Card title="TypeScript SDK" icon="code" href="#typescript-sdk">
    Fully typed SDK with automatic error handling and retry logic
  </Card>

  <Card title="React Hooks" icon="react" href="#react-hooks">
    Pre-built hooks for common operations with React Query integration
  </Card>

  <Card title="Vue Composables" icon="vuejs" href="#vue-composables">
    Vue 3 composables for reactive API integration
  </Card>

  <Card title="REST API" icon="api" href="#direct-api-integration">
    Direct REST API integration for any framework
  </Card>
</CardGroup>

## TypeScript SDK

The official Billbora TypeScript SDK provides the easiest way to integrate with our API.

### Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @billbora/sdk
  ```

  ```bash yarn theme={null}
  yarn add @billbora/sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @billbora/sdk
  ```
</CodeGroup>

### Basic Setup

```typescript theme={null}
import { BillboraClient } from '@billbora/sdk'

const client = new BillboraClient({
  apiKey: 'your-api-key',
  environment: 'production' // or 'sandbox'
})

// Create an invoice
const invoice = await client.invoices.create({
  client: 'client-id',
  line_items: [{
    description: 'Consulting Services',
    quantity: 10,
    unit_price: '150.00'
  }]
})

console.log('Invoice created:', invoice.id)
```

### Configuration Options

```typescript theme={null}
interface BillboraClientConfig {
  apiKey: string
  environment?: 'production' | 'sandbox'
  baseURL?: string
  timeout?: number
  retries?: number
  onError?: (error: BillboraError) => void
  onRequest?: (config: RequestConfig) => void
  onResponse?: (response: Response) => void
}

const client = new BillboraClient({
  apiKey: process.env.BILLBORA_API_KEY!,
  environment: 'production',
  timeout: 30000,
  retries: 3,
  onError: (error) => {
    console.error('API Error:', error)
    // Send to error tracking service
  }
})
```

## React Hooks

For React applications, we provide a comprehensive set of hooks built on React Query.

### Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @billbora/react @tanstack/react-query
  ```

  ```bash yarn theme={null}
  yarn add @billbora/react @tanstack/react-query
  ```
</CodeGroup>

### Provider Setup

```tsx theme={null}
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BillboraProvider } from '@billbora/react'

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <BillboraProvider apiKey="your-api-key">
        <YourApp />
      </BillboraProvider>
    </QueryClientProvider>
  )
}
```

### Invoice Management

```tsx theme={null}
import { useInvoices, useCreateInvoice } from '@billbora/react'

function InvoiceList() {
  const { data: invoices, isLoading } = useInvoices({
    status: 'draft',
    limit: 20
  })
  
  const createInvoice = useCreateInvoice()

  const handleCreateInvoice = async () => {
    try {
      const newInvoice = await createInvoice.mutateAsync({
        client: 'client-id',
        line_items: [{
          description: 'New Service',
          quantity: 1,
          unit_price: '100.00'
        }]
      })
      console.log('Created:', newInvoice.id)
    } catch (error) {
      console.error('Failed to create invoice:', error)
    }
  }

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      <button onClick={handleCreateInvoice}>
        Create Invoice
      </button>
      {invoices?.results.map(invoice => (
        <div key={invoice.id}>
          {invoice.invoice_number} - ${invoice.total_amount}
        </div>
      ))}
    </div>
  )
}
```

### Real-time Updates

```tsx theme={null}
import { useInvoiceSubscription } from '@billbora/react'

function InvoiceDetail({ invoiceId }: { invoiceId: string }) {
  const { data: invoice } = useInvoiceSubscription(invoiceId)

  // Automatically updates when invoice status changes
  return (
    <div>
      <h1>Invoice {invoice?.invoice_number}</h1>
      <p>Status: {invoice?.status}</p>
      <p>Amount: ${invoice?.total_amount}</p>
    </div>
  )
}
```

### Form Integration

```tsx theme={null}
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { createInvoiceSchema } from '@billbora/react/schemas'

function CreateInvoiceForm() {
  const createInvoice = useCreateInvoice()
  
  const form = useForm({
    resolver: zodResolver(createInvoiceSchema),
    defaultValues: {
      client: '',
      line_items: [{
        description: '',
        quantity: 1,
        unit_price: '0.00'
      }]
    }
  })

  const onSubmit = async (data) => {
    try {
      await createInvoice.mutateAsync(data)
      form.reset()
    } catch (error) {
      // Error handling is automatic with the hook
    }
  }

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <input {...form.register('client')} placeholder="Client ID" />
      <input {...form.register('line_items.0.description')} placeholder="Description" />
      <input {...form.register('line_items.0.quantity', { valueAsNumber: true })} type="number" />
      <input {...form.register('line_items.0.unit_price')} placeholder="Price" />
      <button type="submit" disabled={form.formState.isSubmitting}>
        Create Invoice
      </button>
    </form>
  )
}
```

## Vue Composables

For Vue 3 applications, we provide reactive composables.

### Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @billbora/vue @tanstack/vue-query
  ```

  ```bash yarn theme={null}
  yarn add @billbora/vue @tanstack/vue-query
  ```
</CodeGroup>

### Setup

```typescript theme={null}
// main.ts
import { createApp } from 'vue'
import { VueQueryPlugin } from '@tanstack/vue-query'
import { createBillboraPlugin } from '@billbora/vue'

const app = createApp(App)

app.use(VueQueryPlugin)
app.use(createBillboraPlugin({
  apiKey: 'your-api-key'
}))

app.mount('#app')
```

### Usage in Components

```vue theme={null}
<template>
  <div>
    <div v-if="isLoading">Loading invoices...</div>
    <div v-else>
      <div v-for="invoice in invoices?.results" :key="invoice.id">
        {{ invoice.invoice_number }} - ${{ invoice.total_amount }}
      </div>
    </div>
    <button @click="createNewInvoice" :disabled="isCreating">
      Create Invoice
    </button>
  </div>
</template>

<script setup lang="ts">
import { useInvoices, useCreateInvoice } from '@billbora/vue'

const { data: invoices, isLoading } = useInvoices({
  status: 'draft'
})

const { mutateAsync: createInvoice, isLoading: isCreating } = useCreateInvoice()

const createNewInvoice = async () => {
  try {
    await createInvoice({
      client: 'client-id',
      line_items: [{
        description: 'Vue Service',
        quantity: 1,
        unit_price: '200.00'
      }]
    })
  } catch (error) {
    console.error('Failed:', error)
  }
}
</script>
```

## Direct API Integration

For other frameworks or vanilla JavaScript, you can integrate directly with our REST API.

### Fetch API Example

```javascript theme={null}
class BillboraAPI {
  constructor(apiKey, baseURL = 'https://api.billbora.com/api/v1') {
    this.apiKey = apiKey
    this.baseURL = baseURL
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`
    const config = {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    }

    const response = await fetch(url, config)
    
    if (!response.ok) {
      const error = await response.json()
      throw new Error(`API Error: ${error.error.message}`)
    }

    return response.json()
  }

  // Invoice methods
  async getInvoices(params = {}) {
    const query = new URLSearchParams(params).toString()
    return this.request(`/invoices/?${query}`)
  }

  async createInvoice(data) {
    return this.request('/invoices/', {
      method: 'POST',
      body: JSON.stringify(data)
    })
  }

  async getInvoice(id) {
    return this.request(`/invoices/${id}/`)
  }

  // Client methods
  async getClients(params = {}) {
    const query = new URLSearchParams(params).toString()
    return this.request(`/clients/?${query}`)
  }

  async createClient(data) {
    return this.request('/clients/', {
      method: 'POST',
      body: JSON.stringify(data)
    })
  }
}

// Usage
const api = new BillboraAPI('your-api-key')

// Create a client
const client = await api.createClient({
  name: 'Acme Corp',
  email: 'billing@acme.com'
})

// Create an invoice
const invoice = await api.createInvoice({
  client: client.id,
  line_items: [{
    description: 'Consulting',
    quantity: 10,
    unit_price: '150.00'
  }]
})
```

### Axios Example

```javascript theme={null}
import axios from 'axios'

// Create axios instance
const billboraAPI = axios.create({
  baseURL: 'https://api.billbora.com/api/v1',
  headers: {
    'Authorization': `Bearer ${process.env.BILLBORA_API_KEY}`,
    'Content-Type': 'application/json'
  }
})

// Add response interceptor for error handling
billboraAPI.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.data?.error) {
      throw new Error(error.response.data.error.message)
    }
    throw error
  }
)

// Usage
async function createInvoice(invoiceData) {
  try {
    const response = await billboraAPI.post('/invoices/', invoiceData)
    return response.data
  } catch (error) {
    console.error('Failed to create invoice:', error.message)
    throw error
  }
}
```

## Authentication Integration

### JWT Authentication for Frontend Apps

For frontend applications that need user authentication:

```typescript theme={null}
import { createClient } from '@supabase/supabase-js'

class BillboraAuth {
  private supabase = createClient(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_ANON_KEY!
  )
  
  async login(email: string, password: string) {
    // Step 1: Authenticate with Supabase
    const { data, error } = await this.supabase.auth.signInWithPassword({
      email, password
    })
    
    if (error) throw error

    // Step 2: Exchange for Billbora token
    const response = await fetch('/api/v1/users/auth/exchange/', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        supabase_token: data.session.access_token
      })
    })

    const tokens = await response.json()
    
    // Store tokens securely
    localStorage.setItem('billbora_tokens', JSON.stringify(tokens))
    
    return tokens
  }
  
  async logout() {
    await this.supabase.auth.signOut()
    localStorage.removeItem('billbora_tokens')
  }
  
  getAccessToken() {
    const tokens = JSON.parse(localStorage.getItem('billbora_tokens') || '{}')
    return tokens.access
  }
}

// Usage with API client
const auth = new BillboraAuth()
const tokens = await auth.login('user@example.com', 'password')

const client = new BillboraClient({
  accessToken: tokens.access
})
```

## Error Handling

### Global Error Handler

```typescript theme={null}
interface ErrorHandler {
  onValidationError: (errors: Record<string, string[]>) => void
  onNotFound: (resource: string) => void
  onRateLimit: (retryAfter: number) => void
  onUnauthorized: () => void
  onServerError: (error: Error) => void
}

const errorHandler: ErrorHandler = {
  onValidationError: (errors) => {
    // Show field-specific errors in your form
    Object.entries(errors).forEach(([field, messages]) => {
      showFieldError(field, messages[0])
    })
  },
  
  onNotFound: (resource) => {
    showNotification(`${resource} not found`, 'error')
  },
  
  onRateLimit: (retryAfter) => {
    showNotification(`Rate limited. Try again in ${retryAfter} seconds`, 'warning')
  },
  
  onUnauthorized: () => {
    // Redirect to login
    window.location.href = '/login'
  },
  
  onServerError: (error) => {
    showNotification('Something went wrong. Please try again.', 'error')
    console.error('Server error:', error)
  }
}

// Configure client with error handler
const client = new BillboraClient({
  apiKey: 'your-api-key',
  errorHandler
})
```

## Performance Optimization

### Caching Strategies

```typescript theme={null}
// React Query with custom cache configuration
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 10 * 60 * 1000, // 10 minutes
      retry: (failureCount, error) => {
        // Don't retry on 4xx errors
        if (error.status >= 400 && error.status < 500) {
          return false
        }
        return failureCount < 3
      }
    }
  }
})

// Custom cache keys for better invalidation
const invoiceKeys = {
  all: ['invoices'] as const,
  lists: () => [...invoiceKeys.all, 'list'] as const,
  list: (filters: string) => [...invoiceKeys.lists(), filters] as const,
  details: () => [...invoiceKeys.all, 'detail'] as const,
  detail: (id: string) => [...invoiceKeys.details(), id] as const,
}

// Usage
const { data: invoices } = useQuery({
  queryKey: invoiceKeys.list(JSON.stringify(filters)),
  queryFn: () => getInvoices(filters)
})
```

### Request Batching

```typescript theme={null}
class BatchedBillboraClient {
  private batchQueue: Array<{
    resolve: Function
    reject: Function
    request: any
  }> = []
  
  private batchTimeout: NodeJS.Timeout | null = null

  async batchRequest(request: any) {
    return new Promise((resolve, reject) => {
      this.batchQueue.push({ resolve, reject, request })
      
      if (!this.batchTimeout) {
        this.batchTimeout = setTimeout(() => {
          this.processBatch()
        }, 50) // Batch requests over 50ms
      }
    })
  }

  private async processBatch() {
    const batch = this.batchQueue.splice(0)
    this.batchTimeout = null

    try {
      const response = await fetch('/api/v1/batch/', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          requests: batch.map(item => item.request)
        })
      })

      const results = await response.json()
      
      batch.forEach((item, index) => {
        const result = results[index]
        if (result.error) {
          item.reject(new Error(result.error.message))
        } else {
          item.resolve(result.data)
        }
      })
    } catch (error) {
      batch.forEach(item => item.reject(error))
    }
  }
}
```

## Testing

### Mock API Responses

```typescript theme={null}
// Create mock client for testing
export class MockBillboraClient extends BillboraClient {
  private mockResponses = new Map()

  setMockResponse(endpoint: string, response: any) {
    this.mockResponses.set(endpoint, response)
  }

  async request(endpoint: string, options?: any) {
    const mockResponse = this.mockResponses.get(endpoint)
    if (mockResponse) {
      if (mockResponse instanceof Error) {
        throw mockResponse
      }
      return mockResponse
    }
    
    return super.request(endpoint, options)
  }
}

// Usage in tests
const mockClient = new MockBillboraClient({ apiKey: 'test' })

mockClient.setMockResponse('/invoices/', {
  results: [
    { id: 'inv_1', invoice_number: 'INV-001', total_amount: '100.00' }
  ],
  count: 1
})

// Test your components with mock data
```

### Integration Tests

```typescript theme={null}
import { render, screen, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BillboraProvider } from '@billbora/react'

function createTestWrapper() {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false }
    }
  })

  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      <BillboraProvider apiKey="test-key">
        {children}
      </BillboraProvider>
    </QueryClientProvider>
  )
}

test('displays invoices', async () => {
  render(<InvoiceList />, { wrapper: createTestWrapper() })
  
  await waitFor(() => {
    expect(screen.getByText('INV-001')).toBeInTheDocument()
  })
})
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication Setup" icon="key" href="/authentication">
    Configure authentication for your frontend application
  </Card>

  <Card title="Webhook Integration" icon="webhook" href="/webhooks/setup">
    Set up real-time updates with webhooks
  </Card>

  <Card title="Payment Processing" icon="credit-card" href="/resources/payments">
    Accept payments directly in your application
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference">
    Explore all available endpoints and methods
  </Card>
</CardGroup>

## Support

Need help with your integration?

<CardGroup cols={3}>
  <Card title="Example Apps" icon="code" href="https://github.com/billbora/examples">
    Check out complete example applications
  </Card>

  <Card title="Community" icon="users" href="https://community.billbora.com">
    Get help from other developers
  </Card>

  <Card title="Support" icon="life-ring" href="mailto:api-support@billbora.com">
    Contact our support team
  </Card>
</CardGroup>
