import { ref, type Ref } from 'vue' import { type ApiRequestError, normalizeError } from '@/services/error' import { useSnackbarStore } from '@/stores/snackbar' type ToastLevel = 'info' | 'warning' | 'error' type Options = { showErrorToast?: boolean errorToastLevel?: (error: ApiRequestError) => ToastLevel } interface UseApiCallResult { loading: Ref data: Ref error: Ref execute: (...args: TArgs) => Promise executeSafe: (...args: TArgs) => Promise reset: () => void } function getDefaultToastLevel(error: ApiRequestError): ToastLevel { if (typeof error.status === 'number' && error.status >= 500) return 'error' return 'warning' } function levelToColor(level: ToastLevel): string { if (level === 'error') return 'error' if (level === 'warning') return 'warning' return 'info' } export function useApiCall( action: (...args: TArgs) => Promise, options?: Options ): UseApiCallResult { const loading = ref(false) const data = ref(null) as Ref const error = ref(null) const snackbar = useSnackbarStore() const execute = async (...args: TArgs): Promise => { loading.value = true error.value = null try { const result = await action(...args) data.value = result return result } catch (error_) { const normalized = normalizeError(error_) error.value = normalized const showErrorToast = options?.showErrorToast ?? true if (showErrorToast && normalized.name !== 'CanceledRequestError') { const level = (options?.errorToastLevel ?? getDefaultToastLevel)(normalized) snackbar.show({ message: normalized.message, color: levelToColor(level), timeout: 3000, location: 'top right', variant: 'flat', }) } throw normalized } finally { loading.value = false } } const executeSafe = async (...args: TArgs): Promise => { try { return await execute(...args) } catch { return null } } const reset = () => { loading.value = false data.value = null error.value = null } return { loading, data, error, execute, executeSafe, reset, } }