Skip to content

Composables Overview

God Panel provides a comprehensive set of Vue composables that follow the Composition API patterns. These composables handle API integration, state management, UI interactions, and common utility functions.

Composable Categories

🔗 API & Data Composables

  • useApi: Generic API calls with loading states and error handling
  • useAuth: Authentication operations (login, logout, profile)
  • useUsers: User management operations (CRUD operations)
  • useDashboard: Dashboard data fetching and analytics
  • useSettings: User settings and preferences management
  • useApiData: Reactive data fetching with caching support

🛠️ Utility Composables

  • useBoolean: Boolean state management with toggle functions
  • useLocalStorage: Persistent localStorage state management
  • useDynamicFonts: Dynamic font loading and management
  • useErrorHandler: Centralized error handling and reporting
  • useToast: Toast notification management

API Composables

useApi

Generic composable for API calls with reactive loading states and error handling.

vue
<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <pre>{{ JSON.stringify(data, null, 2) }}</pre>
    </div>

    <button @click="fetchData">Refresh</button>
  </div>
</template>

<script setup>
import { useApi } from '~/composables/useApi'

const { data, loading, error, execute } = useApi()

const fetchData = async () => {
  await execute(async () => {
    const response = await $fetch('/api/users')
    return response
  }, {
    onSuccess: (result) => {
      console.log('Data fetched:', result)
    },
    onError: (error) => {
      console.error('Failed to fetch:', error)
    }
  })
}

// Auto-fetch on mount
onMounted(() => {
  fetchData()
})
</script>

Return Values

PropertyTypeDescription
dataRef<T | null>The API response data
loadingRef<boolean>Loading state
errorRef<string | null>Error message if any
executeFunctionExecute API call function
resetFunctionReset all state

Options

typescript
interface ExecuteOptions {
  onSuccess?: (result: any) => void
  onError?: (error: string) => void
  immediate?: boolean
}

useAuth

Composable for authentication operations and session management.

vue
<template>
  <div>
    <div v-if="!isAuthenticated">
      <input v-model="credentials.email" placeholder="Email" />
      <input v-model="credentials.password" type="password" placeholder="Password" />
      <button @click="login" :disabled="loading">Login</button>
    </div>
    <div v-else>
      <p>Welcome, {{ user?.name }}!</p>
      <button @click="logout">Logout</button>
    </div>
  </div>
</template>

<script setup>
import { useAuth } from '~/composables/useApi'

const { login: authLogin, logout, getProfile, isAuthenticated } = useAuth()
const loading = ref(false)
const user = ref(null)
const credentials = ref({
  email: '',
  password: ''
})

const login = async () => {
  loading.value = true
  try {
    const result = await authLogin(credentials.value)
    if (result.success) {
      user.value = result.user
      await navigateTo('/dashboard')
    }
  } catch (error) {
    console.error('Login failed:', error)
  } finally {
    loading.value = false
  }
}

const logoutHandler = async () => {
  await logout()
  user.value = null
  await navigateTo('/auth/login')
}

// Get user profile
const loadProfile = async () => {
  try {
    const profile = await getProfile()
    user.value = profile
  } catch (error) {
    console.error('Failed to load profile:', error)
  }
}

onMounted(() => {
  loadProfile()
})
</script>

Available Methods

MethodParametersDescription
logincredentials: { email: string, password: string }Authenticate user
logout-Clear session and logout
refreshToken-Refresh authentication token
getProfile-Get current user profile
isAuthenticated-Check if user is authenticated

useUsers

Composable for user management operations.

vue
<template>
  <div>
    <div v-for="user in users" :key="user.id">
      {{ user.name }} - {{ user.email }}
    </div>

    <button @click="loadUsers">Load Users</button>
    <button @click="createUser">Create User</button>
  </div>
</template>

<script setup>
import { useUsers } from '~/composables/useApi'

const {
  getUsers,
  getUser,
  createUser,
  updateUser,
  deleteUser
} = useUsers()

const users = ref([])
const loading = ref(false)

const loadUsers = async () => {
  loading.value = true
  try {
    const result = await getUsers({
      page: 1,
      limit: 10,
      role: 'admin'
    })
    users.value = result.data
  } catch (error) {
    console.error('Failed to load users:', error)
  } finally {
    loading.value = false
  }
}

const createUserHandler = async () => {
  try {
    const newUser = await createUser({
      name: 'John Doe',
      email: 'john@example.com',
      role: 'user'
    })
    users.value.push(newUser)
  } catch (error) {
    console.error('Failed to create user:', error)
  }
}
</script>

useDashboard

Composable for dashboard data and analytics.

vue
<template>
  <div class="dashboard">
    <div class="stats-grid">
      <div class="stat-card">
        <h3>Total Users</h3>
        <span>{{ stats.totalUsers }}</span>
      </div>
      <div class="stat-card">
        <h3>Revenue</h3>
        <span>${{ stats.totalRevenue }}</span>
      </div>
    </div>

    <div class="chart-container">
      <!-- Chart component -->
    </div>
  </div>
</template>

<script setup>
import { useDashboard } from '~/composables/useApi'

const {
  getStats,
  getOverview,
  getAnalytics,
  refreshData
} = useDashboard()

const stats = ref({})
const overview = ref({})
const loading = ref(false)

const loadDashboardData = async () => {
  loading.value = true
  try {
    const [statsData, overviewData] = await Promise.all([
      getStats(),
      getOverview()
    ])

    stats.value = statsData
    overview.value = overviewData
  } catch (error) {
    console.error('Failed to load dashboard data:', error)
  } finally {
    loading.value = false
  }
}

const loadAnalytics = async () => {
  try {
    const analytics = await getAnalytics({
      period: '30d',
      metrics: ['users', 'revenue', 'conversion']
    })
    // Process analytics data for charts
  } catch (error) {
    console.error('Failed to load analytics:', error)
  }
}

onMounted(() => {
  loadDashboardData()
  loadAnalytics()
})
</script>

useSettings

Composable for user settings and preferences management.

vue
<template>
  <div class="settings">
    <div class="setting-group">
      <label>Theme</label>
      <select v-model="settings.theme" @change="updateSettings">
        <option value="light">Light</option>
        <option value="dark">Dark</option>
        <option value="auto">Auto</option>
      </select>
    </div>

    <div class="setting-group">
      <label>Language</label>
      <select v-model="settings.language" @change="updateSettings">
        <option value="en">English</option>
        <option value="es">Spanish</option>
        <option value="fr">French</option>
      </select>
    </div>
  </div>
</template>

<script setup>
import { useSettings } from '~/composables/useApi'

const {
  getUserSettings,
  updateUserSettings,
  getPreferences,
  updatePreferences
} = useSettings()

const settings = ref({
  theme: 'light',
  language: 'en',
  notifications: true
})

const loadSettings = async () => {
  try {
    const userSettings = await getUserSettings()
    settings.value = { ...settings.value, ...userSettings }
  } catch (error) {
    console.error('Failed to load settings:', error)
  }
}

const updateSettingsHandler = async () => {
  try {
    await updateUserSettings(settings.value)
    // Show success message
  } catch (error) {
    console.error('Failed to update settings:', error)
  }
}

onMounted(() => {
  loadSettings()
})
</script>

useApiData

Reactive data fetching composable with caching and auto-refresh.

vue
<template>
  <div>
    <div v-if="loading">Loading posts...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <article v-for="post in data" :key="post.id">
        <h3>{{ post.title }}</h3>
        <p>{{ post.content }}</p>
      </article>
    </div>

    <button @click="refetch">Refresh</button>
  </div>
</template>

<script setup>
import { useApiData } from '~/composables/useApi'

const {
  data,
  loading,
  error,
  refetch,
  reset
} = useApiData(
  () => $fetch('/api/posts'),
  {
    immediate: true,
    errorHandler: (error) => {
      console.error('Failed to fetch posts:', error)
    }
  }
)

// Refetch data every 30 seconds
const { pause, resume } = useIntervalFn(refetch, 30000)
</script>

Utility Composables

useBoolean

Simple boolean state management with utility functions.

vue
<template>
  <div>
    <button @click="toggle">Toggle</button>
    <button @click="setTrue">Set True</button>
    <button @click="setFalse">Set False</button>
    <p>Value: {{ value }}</p>
  </div>
</template>

<script setup>
import { useBoolean } from '~/composables/useBoolean'

const {
  value,
  setValue,
  setTrue,
  setFalse,
  toggle
} = useBoolean(false)

// Direct value manipulation
setValue(true)
setValue((prev) => !prev) // Functional update
</script>

Usage Examples

vue
<script setup>
// Modal visibility
const { value: isModalOpen, toggle, setTrue, setFalse } = useBoolean()

// Form state
const { value: isSubmitting, setTrue, setFalse } = useBoolean()

// Loading states
const { value: isLoading, setTrue: startLoading, setFalse: stopLoading } = useBoolean()

// Feature toggles
const { value: isFeatureEnabled } = useBoolean(true)
</script>

useLocalStorage

Persistent state management using localStorage with SSR safety.

vue
<template>
  <div>
    <input v-model="userPreferences.theme" placeholder="Theme" />
    <input v-model="userPreferences.language" placeholder="Language" />
    <p>Settings are automatically saved to localStorage</p>
  </div>
</template>

<script setup>
import { useLocalStorage } from '~/composables/useLocalStorage'

const userPreferences = useLocalStorage('user-preferences', {
  theme: 'light',
  language: 'en',
  notifications: true
})

// Functional updates
const updateTheme = (newTheme: string) => {
  userPreferences.setValue((prev) => ({
    ...prev,
    theme: newTheme
  }))
}

// Reset to defaults
const resetPreferences = () => {
  userPreferences.removeValue()
}
</script>

Features

  • SSR Safe: Works correctly during server-side rendering
  • Type Safe: Full TypeScript support
  • Functional Updates: Support for functional state updates
  • Error Handling: Graceful handling of localStorage errors
  • Reactive: Fully reactive state management

Usage Patterns

vue
<script setup>
// Simple values
const isDarkMode = useLocalStorage('dark-mode', false)

// Complex objects
const settings = useLocalStorage('app-settings', {
  theme: 'light',
  fontSize: 14,
  sidebarCollapsed: false
})

// Arrays
const favoriteItems = useLocalStorage('favorites', [])

// Functional updates
const addFavorite = (item: any) => {
  favoriteItems.setValue((prev) => [...prev, item])
}

const removeFavorite = (itemId: string) => {
  favoriteItems.setValue((prev) => prev.filter(item => item.id !== itemId))
}
</script>

useDynamicFonts

Dynamic font loading and management with Google Fonts integration.

vue
<template>
  <div>
    <select v-model="selectedFont" @change="loadFont">
      <option v-for="font in availableFonts" :key="font" :value="font">
        {{ font }}
      </option>
    </select>
    <p>Current font: {{ selectedFont }}</p>
    <p>Available fonts: {{ availableFonts.join(', ') }}</p>
  </div>
</template>

<script setup>
import { useDynamicFonts } from '~/composables/useDynamicFonts'

const {
  loadFont,
  getFontDefinition,
  getAvailableFonts,
  loadedFonts
} = useDynamicFonts()

const selectedFont = ref('Inter')
const availableFonts = getAvailableFonts()

// Load font on mount
onMounted(() => {
  loadFont(selectedFont.value)
})

// Watch for font changes
watch(selectedFont, (newFont) => {
  if (newFont) {
    loadFont(newFont)
  }
})
</script>

Available Fonts

FontWeightsUse Case
Inter400, 500, 600, 700Modern, clean interface
Roboto400, 500, 700Google's design system
Poppins400, 500, 600, 700Friendly, rounded
Barlow400, 500, 600, 700Contemporary, geometric
DM Sans400, 500, 700Humanist, readable
Nunito Sans400, 600, 700Friendly, rounded

Font Definition Interface

typescript
interface FontDefinition {
  name: string
  family: string
  url?: string
  weights?: string[]
}

useErrorHandler

Centralized error handling with toast notifications and logging.

vue
<template>
  <div>
    <button @click="performRiskyOperation">
      Perform Risky Operation
    </button>
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script setup>
import { useErrorHandler } from '~/composables/useErrorHandler'

const { handleError, handleAsync, withErrorBoundary } = useErrorHandler()

const performRiskyOperation = async () => {
  try {
    // This might fail
    await riskyApiCall()
  } catch (error) {
    handleError(error, {
      showToast: true,
      toastMessage: 'Operation failed. Please try again.',
      logError: true,
      context: {
        operation: 'risky-operation',
        userId: currentUser.value?.id
      }
    })
  }
}

// Wrap async operations
const safeAsyncOperation = async () => {
  return handleAsync(
    () => riskyApiCall(),
    {
      showToast: true,
      toastMessage: 'Data saved successfully!',
      logError: true,
      onSuccess: (result) => {
        console.log('Operation succeeded:', result)
      },
      onError: (error) => {
        console.log('Operation failed:', error)
      }
    }
  )
}

// Error boundary for components
const safeComponentMethod = withErrorBoundary(
  riskyComponentMethod,
  {
    showToast: true,
    fallback: 'Fallback content'
  }
)
</script>

Error Handling Options

typescript
interface ErrorHandlerOptions {
  showToast?: boolean
  toastMessage?: string
  logError?: boolean
  context?: Record<string, any>
  onSuccess?: (result: any) => void
  onError?: (error: AppError) => void
}

useToast

Toast notification management with multiple types and positions.

vue
<template>
  <div>
    <button @click="showSuccessToast">Success</button>
    <button @click="showErrorToast">Error</button>
    <button @click="showWarningToast">Warning</button>
    <button @click="showInfoToast">Info</button>
    <button @click="showLoadingToast">Loading</button>
  </div>
</template>

<script setup>
import { useToast } from '~/composables/useToast'

const {
  success,
  error,
  warning,
  info,
  loading,
  promise,
  removeToast,
  clearAll,
  toasts,
  updateConfig
} = useToast()

// Simple notifications
const showSuccessToast = () => {
  success('Operation completed successfully!')
}

const showErrorToast = () => {
  error('Something went wrong', {
    title: 'Error',
    duration: 8000,
    position: 'top-center'
  })
}

const showWarningToast = () => {
  warning('Please check your input', {
    title: 'Warning',
    actions: [
      { label: 'Fix', onClick: () => console.log('Fix clicked') }
    ]
  })
}

const showInfoToast = () => {
  info('New update available', {
    title: 'Update',
    duration: 10000,
    position: 'top-right'
  })
}

// Loading toast
const showLoadingToast = () => {
  loading('Processing...', {
    title: 'Loading',
    position: 'top-center'
  })
}

// Promise-based toast
const handleAsyncOperation = async () => {
  const result = await promise(
    asyncOperation(),
    {
      loading: 'Saving data...',
      success: 'Data saved successfully!',
      error: 'Failed to save data'
    }
  )
  return result
}

// Toast management
const dismissToast = (id: string) => {
  removeToast(id)
}

const clearAllToasts = () => {
  clearAll()
}

// Configure toast settings
updateConfig({
  position: 'bottom-right',
  duration: 5000,
  maxToasts: 3
})
</script>

Toast Types

TypeColorIconUse Case
successGreenCheck circleSuccessful operations
errorRedAlert circleErrors and failures
warningOrangeAlert triangleWarnings and cautions
infoBlueInfo circleInformation and updates
loadingPrimarySpinnerLoading states

Toast Positions

  • top-left, top-center, top-right
  • bottom-left, bottom-center, bottom-right

Advanced Usage

Combining Composables

vue
<script setup>
// Combine multiple composables for complex functionality
const { data, loading, error, execute } = useApi()
const { success, error: showError } = useToast()
const { handleError } = useErrorHandler()

const fetchUserData = async () => {
  await execute(async () => {
    return await $fetch(`/api/users/${userId.value}`)
  }, {
    onSuccess: (userData) => {
      success(`User ${userData.name} loaded successfully!`)
    },
    onError: (errorMessage) => {
      handleError(new Error(errorMessage), {
        showToast: false // Already shown by execute
      })
    }
  })
}
</script>

Reactive Data with Caching

vue
<script setup>
// Auto-refreshing data with caching
const { data: users, refetch } = useApiData(
  () => $fetch('/api/users'),
  {
    immediate: true
  }
)

// Refresh data every 30 seconds
const { pause, resume } = useIntervalFn(refetch, 30000)

// Manual refresh with loading state
const refreshUsers = async () => {
  await refetch()
  success('Users refreshed!')
}
</script>

Global State Management

vue
<script setup>
// Persistent settings across the app
const theme = useLocalStorage('app-theme', 'light')
const fontSize = useLocalStorage('font-size', 16)
const sidebarCollapsed = useLocalStorage('sidebar-collapsed', false)

// Reactive settings
watch(theme.value, (newTheme) => {
  // Apply theme changes
  document.documentElement.setAttribute('data-theme', newTheme)
})

watch(fontSize.value, (newSize) => {
  // Apply font size changes
  document.documentElement.style.fontSize = `${newSize}px`
})
</script>

Best Practices

API Composables

  1. Always handle loading states in UI components
  2. Provide meaningful error messages for users
  3. Use appropriate caching for frequently accessed data
  4. Implement retry logic for critical operations

State Management

  1. Use useLocalStorage for persistent user preferences
  2. Prefer useBoolean for simple boolean state
  3. Use reactive references for complex state objects
  4. Implement proper cleanup for subscriptions

Error Handling

  1. Use useErrorHandler for consistent error management
  2. Provide context when logging errors
  3. Show user-friendly messages via toasts
  4. Implement fallback UI for error states

Performance

  1. Lazy load heavy composables when needed
  2. Use computed properties for derived state
  3. Implement proper cleanup in composables
  4. Monitor memory usage for long-running applications

Testing Composables

vue
<script setup>
// Example composable test
describe('useBoolean', () => {
  it('should manage boolean state correctly', () => {
    const { value, setTrue, setFalse, toggle } = useBoolean(false)

    expect(value.value).toBe(false)

    setTrue()
    expect(value.value).toBe(true)

    setFalse()
    expect(value.value).toBe(false)

    toggle()
    expect(value.value).toBe(true)
  })
})

describe('useLocalStorage', () => {
  it('should persist state to localStorage', () => {
    const testValue = useLocalStorage('test-key', 'default')

    testValue.setValue('new value')
    expect(testValue.value.value).toBe('new value')

    // Simulate page reload
    const newInstance = useLocalStorage('test-key', 'default')
    expect(newInstance.value.value).toBe('new value')
  })
})
</script>

Troubleshooting

Common Issues

Composables not reactive:

  • Ensure you're using ref() or reactive() properly
  • Check that you're accessing .value for refs
  • Verify composable is properly imported

LocalStorage not persisting:

  • Check browser console for localStorage errors
  • Verify SSR compatibility
  • Ensure JSON serializable data

API calls not working:

  • Verify API endpoints are correct
  • Check authentication tokens
  • Review error handling

Toast notifications not showing:

  • Ensure ToastContainer is mounted in your app
  • Check toast service configuration
  • Verify toast options are valid

Integration Examples

Complete User Management

vue
<template>
  <div class="user-management">
    <!-- User List -->
    <div v-if="loading">Loading users...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <div v-for="user in users" :key="user.id" class="user-card">
        <h3>{{ user.name }}</h3>
        <p>{{ user.email }}</p>
        <button @click="editUser(user)">Edit</button>
        <button @click="deleteUser(user.id)">Delete</button>
      </div>
    </div>

    <!-- Add User Form -->
    <form @submit="createUser" v-if="showForm">
      <input v-model="newUser.name" placeholder="Name" required />
      <input v-model="newUser.email" placeholder="Email" type="email" required />
      <button type="submit" :disabled="formLoading">Create</button>
    </form>
  </div>
</template>

<script setup>
import { useApi, useUsers } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { useBoolean } from '~/composables/useBoolean'

const { success, error: showError } = useToast()
const { getUsers, createUser: createUserApi, deleteUser: deleteUserApi } = useUsers()
const { value: showForm, setTrue: showFormFn, setFalse: hideForm } = useBoolean()

const {
  data: users,
  loading,
  error,
  execute: loadUsers
} = useApi()

const formLoading = ref(false)
const newUser = ref({
  name: '',
  email: ''
})

const loadUsersData = async () => {
  await loadUsers(async () => {
    return await getUsers({ page: 1, limit: 50 })
  })
}

const createUserHandler = async () => {
  formLoading.value = true
  try {
    await createUserApi(newUser.value)
    success('User created successfully!')
    newUser.value = { name: '', email: '' }
    hideForm()
    await loadUsersData() // Refresh list
  } catch (err) {
    showError('Failed to create user')
  } finally {
    formLoading.value = false
  }
}

const deleteUserHandler = async (userId: string) => {
  if (!confirm('Are you sure you want to delete this user?')) return

  try {
    await deleteUserApi(userId)
    success('User deleted successfully!')
    await loadUsersData() // Refresh list
  } catch (err) {
    showError('Failed to delete user')
  }
}

onMounted(() => {
  loadUsersData()
})
</script>

Settings Management

vue
<script setup>
// Complete settings management example
import { useSettings } from '~/composables/useApi'
import { useLocalStorage } from '~/composables/useLocalStorage'
import { useDynamicFonts } from '~/composables/useDynamicFonts'
import { useToast } from '~/composables/useToast'

const {
  updateUserSettings,
  getUserSettings
} = useSettings()

const { loadFont, getAvailableFonts } = useDynamicFonts()
const { success } = useToast()

// Persistent settings
const theme = useLocalStorage('theme', 'light')
const fontFamily = useLocalStorage('font-family', 'Inter')
const fontSize = useLocalStorage('font-size', 16)
const sidebarCollapsed = useLocalStorage('sidebar-collapsed', false)

// Available fonts
const availableFonts = getAvailableFonts()

// Watch for theme changes
watch(theme.value, (newTheme) => {
  document.documentElement.setAttribute('data-theme', newTheme)
})

// Watch for font changes
watch(fontFamily.value, (newFont) => {
  loadFont(newFont)
})

// Save settings to server
const saveSettings = async () => {
  try {
    await updateUserSettings({
      theme: theme.value.value,
      fontFamily: fontFamily.value.value,
      fontSize: fontSize.value.value,
      sidebarCollapsed: sidebarCollapsed.value.value
    })
    success('Settings saved successfully!')
  } catch (error) {
    console.error('Failed to save settings:', error)
  }
}

// Load settings from server
const loadSettings = async () => {
  try {
    const serverSettings = await getUserSettings()
    theme.setValue(serverSettings.theme || 'light')
    fontFamily.setValue(serverSettings.fontFamily || 'Inter')
    fontSize.setValue(serverSettings.fontSize || 16)
    sidebarCollapsed.setValue(serverSettings.sidebarCollapsed || false)
  } catch (error) {
    console.error('Failed to load settings:', error)
  }
}

onMounted(() => {
  loadSettings()
  loadFont(fontFamily.value.value)
})
</script>

Next Steps

Released under the MIT License.