94 lines
2.4 KiB
TypeScript
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,
|
|
}
|
|
}
|