Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b59d0f8a44 | |||
| 5cb8cff4ce | |||
| c62f054da1 | |||
| be57451d29 |
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.27'
|
app.version: '0.1.29'
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
<div class="h-full flex-1 flex flex-col min-h-0 min-w-0">
|
<div class="h-full flex-1 flex flex-col min-h-0 min-w-0">
|
||||||
<main
|
<main
|
||||||
class="flex flex-1 flex-col overflow-y-auto overflow-x-hidden bg-white px-4 pb-24 sm:px-8 lg:px-16">
|
class="flex flex-1 flex-col overflow-y-auto overflow-x-hidden bg-white px-4 pb-24 sm:px-8 lg:px-16">
|
||||||
<div aria-hidden="true"
|
<div
|
||||||
class="pointer-events-none sticky top-0 z-30 h-8 flex-shrink-0 bg-white sm:h-12"/>
|
aria-hidden="true"
|
||||||
|
class="pointer-events-none sticky top-0 z-30 h-8 flex-shrink-0 bg-white sm:h-12"/>
|
||||||
<slot/>
|
<slot/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mx-auto w-full max-w-lg">
|
<div class="mx-auto w-full max-w-lg">
|
||||||
<span
|
<span
|
||||||
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
||||||
>
|
>
|
||||||
<img src="/LOGO_MALIO.png" alt="Logo" class="w-[150px]"/>
|
<img src="/LOGO_MALIO.png" alt="Logo" class="w-[150px]"/>
|
||||||
</span>
|
</span>
|
||||||
<form
|
<form
|
||||||
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
||||||
@submit.prevent="handleSubmit"
|
@submit.prevent="handleSubmit"
|
||||||
@@ -13,16 +13,16 @@
|
|||||||
label="Nom d'utilisateur"
|
label="Nom d'utilisateur"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
group-class="mt-0"
|
group-class="mt-0"
|
||||||
inputClass="w-full"
|
input-class="w-full"
|
||||||
v-model="username"
|
v-model="username"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MalioInputPassword
|
<MalioInputPassword
|
||||||
v-model="password"
|
v-model="password"
|
||||||
label="Mot de passe"
|
label="Mot de passe"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
inputClass="w-full"
|
input-class="w-full"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MalioButton
|
<MalioButton
|
||||||
label="Se connecter"
|
label="Se connecter"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { FetchOptions } from 'ofetch'
|
import type { FetchOptions , FetchError } from 'ofetch'
|
||||||
import { $fetch, FetchError } from 'ofetch'
|
import { $fetch } from 'ofetch'
|
||||||
|
|
||||||
export type AnyObject = Record<string, unknown>
|
export type AnyObject = Record<string, unknown>
|
||||||
|
|
||||||
@@ -25,178 +25,178 @@ export type ApiFetchOptions<ResponseType extends 'json' | 'blob'> =
|
|||||||
let isHandlingUnauthorized = false
|
let isHandlingUnauthorized = false
|
||||||
|
|
||||||
export function useApi(): ApiClient {
|
export function useApi(): ApiClient {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const baseURL = config.public.apiBase || '/api'
|
const baseURL = config.public.apiBase || '/api'
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const i18n = nuxtApp.$i18n as
|
const i18n = nuxtApp.$i18n as
|
||||||
| {
|
| {
|
||||||
t: (key: string) => string
|
t: (key: string) => string
|
||||||
te?: (key: string) => boolean
|
te?: (key: string) => boolean
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key)
|
const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key)
|
||||||
const te = (key: string) => (i18n?.te ? i18n.te(key) : false)
|
const te = (key: string) => (i18n?.te ? i18n.te(key) : false)
|
||||||
|
|
||||||
function extractErrorMessage(error: unknown, responseData?: unknown): string {
|
function extractErrorMessage(error: unknown, responseData?: unknown): string {
|
||||||
const data = responseData ?? (error as FetchError)?.data
|
const data = responseData ?? (error as FetchError)?.data
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
const record = data as Record<string, unknown>
|
const record = data as Record<string, unknown>
|
||||||
return (
|
return (
|
||||||
(record['hydra:description'] as string) ||
|
(record['hydra:description'] as string) ||
|
||||||
(record.detail as string) ||
|
(record.detail as string) ||
|
||||||
(record.message as string) ||
|
(record.message as string) ||
|
||||||
(record.error as string) ||
|
(record.error as string) ||
|
||||||
(record.title as string) ||
|
(record.title as string) ||
|
||||||
(record['hydra:title'] as string) ||
|
(record['hydra:title'] as string) ||
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (error as FetchError)?.message ?? 'Erreur inconnue.'
|
||||||
}
|
}
|
||||||
|
|
||||||
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 methodErrorKeys: Record<string, string> = {
|
const client = $fetch.create({
|
||||||
GET: 'errors.http.get',
|
baseURL,
|
||||||
POST: 'errors.http.post',
|
retry: 0,
|
||||||
PUT: 'errors.http.put',
|
credentials: 'include',
|
||||||
PATCH: 'errors.http.patch',
|
onResponse({ options, response }) {
|
||||||
DELETE: 'errors.http.delete'
|
const apiOptions = options as ApiFetchOptions<'json'>
|
||||||
}
|
if (apiOptions?.toast === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const client = $fetch.create({
|
if (response?.status && response.status >= 400) {
|
||||||
baseURL,
|
return
|
||||||
retry: 0,
|
}
|
||||||
credentials: 'include',
|
|
||||||
onResponse({ options, response }) {
|
|
||||||
const apiOptions = options as ApiFetchOptions<'json'>
|
|
||||||
if (apiOptions?.toast === false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.status && response.status >= 400) {
|
const successKey = apiOptions?.toastSuccessKey
|
||||||
return
|
const successMessage =
|
||||||
}
|
|
||||||
|
|
||||||
const successKey = apiOptions?.toastSuccessKey
|
|
||||||
const successMessage =
|
|
||||||
apiOptions?.toastSuccessMessage ||
|
apiOptions?.toastSuccessMessage ||
|
||||||
(successKey ? (te(successKey) ? t(successKey) : successKey) : '')
|
(successKey ? (te(successKey) ? t(successKey) : successKey) : '')
|
||||||
|
|
||||||
if (successMessage) {
|
if (successMessage) {
|
||||||
toast.success({
|
toast.success({
|
||||||
title: 'Succes',
|
title: 'Succes',
|
||||||
message: successMessage
|
message: successMessage
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onResponseError({ response, error, options }) {
|
async onResponseError({ response, error, options }) {
|
||||||
const apiOptions = options as ApiFetchOptions<'json'>
|
const apiOptions = options as ApiFetchOptions<'json'>
|
||||||
if (response?.status === 401) {
|
if (response?.status === 401) {
|
||||||
const requestUrl = typeof options?.url === 'string' ? options.url : ''
|
const requestUrl = typeof options?.url === 'string' ? options.url : ''
|
||||||
const isLoginCheck = requestUrl.includes('/login_check')
|
const isLoginCheck = requestUrl.includes('/login_check')
|
||||||
const isLogout = requestUrl.includes('/logout')
|
const isLogout = requestUrl.includes('/logout')
|
||||||
const shouldToast401 = apiOptions?.toastOn401 === true && apiOptions?.toast !== false
|
const shouldToast401 = apiOptions?.toastOn401 === true && apiOptions?.toast !== false
|
||||||
|
|
||||||
if (shouldToast401) {
|
if (shouldToast401) {
|
||||||
const errorKey = apiOptions?.toastErrorKey
|
const errorKey = apiOptions?.toastErrorKey
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
errorKey ? (te(errorKey) ? t(errorKey) : errorKey) : ''
|
errorKey ? (te(errorKey) ? t(errorKey) : errorKey) : ''
|
||||||
const extractedMessage = extractErrorMessage(error, response?._data)
|
const extractedMessage = extractErrorMessage(error, response?._data)
|
||||||
const message =
|
const message =
|
||||||
apiOptions?.toastErrorMessage ||
|
apiOptions?.toastErrorMessage ||
|
||||||
errorMessage ||
|
errorMessage ||
|
||||||
extractedMessage ||
|
extractedMessage ||
|
||||||
'Une erreur est survenue.'
|
'Une erreur est survenue.'
|
||||||
|
|
||||||
toast.error({
|
toast.error({
|
||||||
title: apiOptions?.toastTitle ?? 'Erreur',
|
title: apiOptions?.toastTitle ?? 'Erreur',
|
||||||
message
|
message
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoginCheck && !isLogout) {
|
if (!isLoginCheck && !isLogout) {
|
||||||
if (!isHandlingUnauthorized) {
|
if (!isHandlingUnauthorized) {
|
||||||
isHandlingUnauthorized = true
|
isHandlingUnauthorized = true
|
||||||
auth.clearSession()
|
auth.clearSession()
|
||||||
await navigateTo('/login')
|
await navigateTo('/login')
|
||||||
isHandlingUnauthorized = false
|
isHandlingUnauthorized = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiOptions?.toast === false) {
|
if (apiOptions?.toast === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const method =
|
const method =
|
||||||
typeof options?.method === 'string' ? options.method.toUpperCase() : 'GET'
|
typeof options?.method === 'string' ? options.method.toUpperCase() : 'GET'
|
||||||
const defaultKey = methodErrorKeys[method]
|
const defaultKey = methodErrorKeys[method]
|
||||||
const defaultMessage =
|
const defaultMessage =
|
||||||
defaultKey && te(defaultKey) ? t(defaultKey) : ''
|
defaultKey && te(defaultKey) ? t(defaultKey) : ''
|
||||||
const errorKey = apiOptions?.toastErrorKey
|
const errorKey = apiOptions?.toastErrorKey
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
errorKey ? (te(errorKey) ? t(errorKey) : errorKey) : ''
|
errorKey ? (te(errorKey) ? t(errorKey) : errorKey) : ''
|
||||||
const extractedMessage = extractErrorMessage(error, response?._data)
|
const extractedMessage = extractErrorMessage(error, response?._data)
|
||||||
const message =
|
const message =
|
||||||
apiOptions?.toastErrorMessage ||
|
apiOptions?.toastErrorMessage ||
|
||||||
errorMessage ||
|
errorMessage ||
|
||||||
defaultMessage ||
|
defaultMessage ||
|
||||||
extractedMessage ||
|
extractedMessage ||
|
||||||
'Une erreur est survenue.'
|
'Une erreur est survenue.'
|
||||||
|
|
||||||
toast.error({
|
toast.error({
|
||||||
title: apiOptions?.toastTitle ?? 'Erreur',
|
title: apiOptions?.toastTitle ?? 'Erreur',
|
||||||
message
|
message
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function request<T>(
|
function request<T>(
|
||||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
||||||
url: string,
|
url: string,
|
||||||
options: ApiFetchOptions<'json'> = {}
|
options: ApiFetchOptions<'json'> = {}
|
||||||
) {
|
) {
|
||||||
const needsJsonBody = method === 'POST' || method === 'PUT'
|
const needsJsonBody = method === 'POST' || method === 'PUT'
|
||||||
const needsMergePatch = method === 'PATCH'
|
const needsMergePatch = method === 'PATCH'
|
||||||
const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData
|
const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData
|
||||||
|
|
||||||
const headers = new Headers(options.headers as HeadersInit | undefined)
|
const headers = new Headers(options.headers as HeadersInit | undefined)
|
||||||
|
|
||||||
if (!isFormData) {
|
if (!isFormData) {
|
||||||
if (needsMergePatch && !headers.has('Content-Type')) {
|
if (needsMergePatch && !headers.has('Content-Type')) {
|
||||||
headers.set('Content-Type', 'application/merge-patch+json')
|
headers.set('Content-Type', 'application/merge-patch+json')
|
||||||
} else if (needsJsonBody && !headers.has('Content-Type')) {
|
} else if (needsJsonBody && !headers.has('Content-Type')) {
|
||||||
headers.set('Content-Type', 'application/json')
|
headers.set('Content-Type', 'application/json')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return client<T>(url, { ...options, method, headers })
|
||||||
}
|
}
|
||||||
|
|
||||||
return client<T>(url, { ...options, method, headers })
|
return {
|
||||||
}
|
get<T>(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||||
|
return request<T>('GET', url, { ...options, query })
|
||||||
return {
|
},
|
||||||
get<T>(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
post<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||||
return request<T>('GET', url, { ...options, query })
|
return request<T>('POST', url, { ...options, body })
|
||||||
},
|
},
|
||||||
post<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
put<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||||
return request<T>('POST', url, { ...options, body })
|
return request<T>('PUT', url, { ...options, body })
|
||||||
},
|
},
|
||||||
put<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
patch<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||||
return request<T>('PUT', url, { ...options, body })
|
return request<T>('PATCH', url, { ...options, body })
|
||||||
},
|
},
|
||||||
patch<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
delete<T>(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||||
return request<T>('PATCH', url, { ...options, body })
|
return request<T>('DELETE', url, { ...options, query })
|
||||||
},
|
}
|
||||||
delete<T>(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
|
||||||
return request<T>('DELETE', url, { ...options, query })
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user