Files
skt-vuetify-templates/src/composables/useApiCall.ts
T

94 lines
2.4 KiB
TypeScript

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<TResult, TArgs extends unknown[]> {
loading: Ref<boolean>
data: Ref<TResult | null>
error: Ref<ApiRequestError | null>
execute: (...args: TArgs) => Promise<TResult>
executeSafe: (...args: TArgs) => Promise<TResult | null>
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<TResult, TArgs extends unknown[]>(
action: (...args: TArgs) => Promise<TResult>,
options?: Options
): UseApiCallResult<TResult, TArgs> {
const loading = ref(false)
const data = ref<TResult | null>(null) as Ref<TResult | null>
const error = ref<ApiRequestError | null>(null)
const snackbar = useSnackbarStore()
const execute = async (...args: TArgs): Promise<TResult> => {
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<TResult | null> => {
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,
}
}