feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur

This commit is contained in:
AUTIN Tristan
2026-01-16 10:18:12 +01:00
parent 94ea49587a
commit 2d3ce2ca43
9 changed files with 2612 additions and 53 deletions
+18
View File
@@ -0,0 +1,18 @@
.iziToast {
font-size: 16px;
min-height: 72px;
}
.iziToast > .iziToast-body {
padding: 18px 24px;
}
.iziToast > .iziToast-body .iziToast-title {
font-size: 18px;
line-height: 1.3;
}
.iziToast > .iziToast-body .iziToast-message {
font-size: 16px;
line-height: 1.5;
}
+52 -1
View File
@@ -16,12 +16,25 @@ export type ApiFetchOptions<ResponseType extends 'json' | 'blob'> =
FetchOptions<ResponseType> & {
toast?: boolean
toastTitle?: string
toastErrorMessage?: string
toastSuccessMessage?: string
toastErrorKey?: string
toastSuccessKey?: string
}
export const useApi = (): ApiClient => {
const config = useRuntimeConfig()
const baseURL = config.public.apiBase ?? '/api'
const toast = useToast()
const nuxtApp = useNuxtApp()
const i18n = nuxtApp.$i18n as
| {
t: (key: string) => string
te?: (key: string) => boolean
}
| undefined
const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key)
const te = (key: string) => (i18n?.te ? i18n.te(key) : false)
const extractErrorMessage = (error: unknown, responseData?: unknown): string => {
const data = responseData ?? (error as FetchError)?.data
@@ -46,17 +59,55 @@ export const useApi = (): ApiClient => {
return (error as FetchError)?.message ?? 'Erreur inconnue.'
}
const methodErrorKeys: Record<string, string> = {
GET: 'errors.http.get',
POST: 'errors.http.post',
PUT: 'errors.http.put',
PATCH: 'errors.http.patch',
DELETE: 'errors.http.delete'
}
const client = $fetch.create({
baseURL,
retry: 0,
onResponse({ options }) {
const apiOptions = options as ApiFetchOptions<'json'>
if (apiOptions?.toast === false) {
return
}
const successKey = apiOptions?.toastSuccessKey
const successMessage =
apiOptions?.toastSuccessMessage ||
(successKey ? (te(successKey) ? t(successKey) : successKey) : '')
if (successMessage) {
toast.success({
title: 'Succès',
message: successMessage
})
}
},
onResponseError({ response, error, options }) {
const apiOptions = options as ApiFetchOptions<'json'>
if (apiOptions?.toast === false) {
return
}
const method =
typeof options?.method === 'string' ? options.method.toUpperCase() : 'GET'
const defaultKey = methodErrorKeys[method]
const defaultMessage =
defaultKey && te(defaultKey) ? t(defaultKey) : ''
const errorKey = apiOptions?.toastErrorKey
const errorMessage =
errorKey ? (te(errorKey) ? t(errorKey) : errorKey) : ''
const extractedMessage = extractErrorMessage(error, response?._data)
const message =
extractErrorMessage(error, response?._data) ||
apiOptions?.toastErrorMessage ||
errorMessage ||
defaultMessage ||
extractedMessage ||
'Une erreur est survenue.'
toast.error({
+23
View File
@@ -0,0 +1,23 @@
{
"errors": {
"http": {
"get": "Impossible de récupérer les données.",
"post": "Impossible de créer la ressource.",
"put": "Impossible de mettre à jour la ressource.",
"patch": "Impossible de mettre à jour la ressource.",
"delete": "Impossible de supprimer la ressource."
},
"reception": {
"list": "Impossible de récupérer la liste des réceptions.",
"fetch": "Impossible de récupérer la réception.",
"create": "Impossible de créer la réception.",
"update": "Impossible de mettre à jour la réception.",
"weigh": "Impossible de récupérer la pesée."
}
},
"success": {
"reception": {
"update": "Réception mise à jour avec succès."
}
}
}
+23 -2
View File
@@ -2,13 +2,34 @@ export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
ssr: false,
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt', 'nuxt-toast'],
modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
'nuxt-toast',
'@nuxtjs/i18n'
],
css: ['~/assets/css/toast.css'],
runtimeConfig: {
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE
}
},
toast: {
settings: {
timeout: 0,
closeOnClick: true,
progressBar: false
}
},
i18n: {
strategy: 'no_prefix',
defaultLocale: 'fr',
langDir: 'locales',
locales: [
{ code: 'fr', file: 'fr.json', name: 'Français' }
]
},
typescript: {
strict: true
}
})
})
+2433 -5
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -11,6 +11,7 @@
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@nuxtjs/i18n": "^10.2.1",
"@pinia/nuxt": "^0.11.3",
"izitoast": "^1.4.0",
"nuxt": "^4.2.2",
+19 -8
View File
@@ -1,28 +1,39 @@
import { useApi } from '~/composables/useApi'
import type { ReceptionData } from '~/services/dto/reception-data'
import type { WeightData } from '~/services/dto/weight-data'
import {useApi} from '~/composables/useApi'
import type {ReceptionData} from '~/services/dto/reception-data'
import type {WeightData} from '~/services/dto/weight-data'
export async function getReceptionList() {
const api = useApi()
return api.get<ReceptionData>(`receptions`)
return api.get<ReceptionData>(`receptions`, {}, {
toastErrorKey: 'errors.reception.list'
})
}
export async function getReception(id: number) {
const api = useApi()
return api.get<ReceptionData>(`receptions/${id}`)
return api.get<ReceptionData>(`receptions/${id}`, {}, {
toastErrorKey: 'errors.reception.fetch'
})
}
export async function createReception(payload: Partial<ReceptionData> = {}) {
const api = useApi()
return api.post<ReceptionData>('receptions', payload)
return api.post<ReceptionData>('receptions', payload, {
toastErrorKey: 'errors.reception.create'
})
}
export async function updateReception(id: number, payload: Partial<ReceptionData>) {
const api = useApi()
return api.patch<ReceptionData>(`receptions/${id}`, payload)
return api.patch<ReceptionData>(`receptions/${id}`, payload, {
toastErrorKey: 'errors.reception.update',
toastSuccessKey: 'success.reception.update'
})
}
export async function getWeight(): Promise<WeightData> {
const api = useApi()
return api.get<WeightData>('receptions/weigh')
return api.get<WeightData>('receptions/weigh', {}, {
toastErrorKey: 'errors.reception.weigh'
})
}