Added check on client side (api)

This commit is contained in:
Alexis
2024-11-24 19:56:00 +01:00
parent 5258ed3f2a
commit c7069164b0
10 changed files with 68 additions and 24 deletions

View File

@@ -1,9 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpleLine, PhTag } from "@phosphor-icons/vue" import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpleLine, PhTag } from "@phosphor-icons/vue"
import { useToast } from "~/components/ui/toast";
import type { APIError } from "~/models/Errors";
const { resetSkeleton, updateEventFromSkeleton, cancelLatestRequest } = useCalendar() const { resetSkeleton, updateEventFromSkeleton, cancelLatestRequest } = useCalendar()
const { eventSkeleton, lastActiveEvent, isEditEventModalOpen } = storeToRefs(useCalendar()) const { eventSkeleton, lastActiveEvent, isEditEventModalOpen } = storeToRefs(useCalendar())
const { toast } = useToast()
const { t } = useI18n()
const isLoading = ref(false) const isLoading = ref(false)
const formErrors = reactive<{ message: string | null }>({ const formErrors = reactive<{ message: string | null }>({
@@ -24,15 +29,22 @@ async function handleAction() {
try { try {
await updateEventFromSkeleton() await updateEventFromSkeleton()
isEditEventModalOpen.value = false
} catch (err) { } catch (err) {
if (err instanceof Error) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
formErrors.message = err.message const apiError = (err as any).data as APIError
}
apiError.data.errors.forEach((error) => {
toast({
title: t("entity.calendar.event.editErrors.toastTitle"),
variant: "destructive",
description: t(`entity.calendar.event.editErrors.${error.path[1]}_${error.code}`),
duration: 2000,
})
})
} finally { } finally {
resetSkeleton() resetSkeleton()
isLoading.value = false isLoading.value = false
isEditEventModalOpen.value = false
} }
} }

View File

@@ -13,5 +13,5 @@ const delegatedProps = computed(() => {
</script> </script>
<template> <template>
<ToastViewport v-bind="delegatedProps" :class="cn('fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)" /> <ToastViewport v-bind="delegatedProps" :class="cn('fixed top-0 z-[100] flex gap-y-2 max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)" />
</template> </template>

View File

@@ -140,6 +140,11 @@ export default defineI18nConfig(() => ({
title: "Edit event", title: "Edit event",
subtitle: "Update event data", subtitle: "Update event data",
}, },
editErrors: {
toastTitle: "Event wasn't updated.",
title_too_big: "Title should be less than 240 characters long.",
description_too_big: "Description should be less than 1200 characters long.",
},
deleteDialog: { deleteDialog: {
title: "Delete this event", title: "Delete this event",
subtitle: "Data associated with this event will be lost and you won't be able to retrieve it !", subtitle: "Data associated with this event will be lost and you won't be able to retrieve it !",
@@ -348,6 +353,11 @@ export default defineI18nConfig(() => ({
title: "Modifier l'évènement", title: "Modifier l'évènement",
subtitle: "Mettre à jour les données de l'évènement", subtitle: "Mettre à jour les données de l'évènement",
}, },
editErrors: {
toastTitle: "L'évènement n'a pas été modifié",
title_too_big: "Le titre doit être inférieur à 240 caractères.",
description_too_big: "La description doit être inférieure à 1200 caractères.",
},
deleteDialog: { deleteDialog: {
title: "Supprimer l'évènement", title: "Supprimer l'évènement",
subtitle: "Les données associés à cet évènement seront supprimées et vous ne pourrez plus les récupérer !", subtitle: "Les données associés à cet évènement seront supprimées et vous ne pourrez plus les récupérer !",

View File

@@ -20,8 +20,8 @@ export interface CalendarEvent {
*/ */
export const postEventBodySchema = z.object({ export const postEventBodySchema = z.object({
event: z.object({ event: z.object({
title: z.string(), title: z.string().max(240),
description: z.string().optional().nullable(), description: z.string().max(1200).optional().nullable(),
location: z.string().optional().nullable(), location: z.string().optional().nullable(),
startDate: dateSchema.required(), startDate: dateSchema.required(),
endDate: dateSchema.optional().nullable(), endDate: dateSchema.optional().nullable(),

14
models/Errors.ts Normal file
View File

@@ -0,0 +1,14 @@
export interface ValidationError {
path: (string | number)[]
message: string
code: string
}
export interface APIError {
statusCode: number
statusMessage: string
message: string
data: {
errors: ValidationError[]
}
}

View File

@@ -1,6 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
devtools: { enabled: true }, devtools: { enabled: false },
modules: [ modules: [
"@nuxtjs/supabase", "@nuxtjs/supabase",

View File

@@ -22,12 +22,22 @@ export default defineEventHandler(async (event) => {
} }
if (bodyError) { if (bodyError) {
throw createError({ const error = createError({
cause: "Utilisateur", cause: "Utilisateur",
fatal: false, fatal: false,
message: "Le schéma de la requête n'est pas complet ou mal renseigné.", statusCode: 401,
status: 401, statusMessage: "Validation Error",
message: "Erreur de validation du schéma, cela est probablement dû à une erreur utilisateur.",
data: {
errors: bodyError.issues.map(issue => ({
path: issue.path,
message: issue.message,
code: issue.code
}))
}
}) })
throw error
} }
try { try {

View File

@@ -11,6 +11,8 @@ export default defineEventHandler(async (event) => {
cause: "Utilisateur", cause: "Utilisateur",
fatal: false, fatal: false,
message: "Le schéma de la requête n'est pas complet ou mal renseigné.", message: "Le schéma de la requête n'est pas complet ou mal renseigné.",
data: schemaError.issues,
statusMessage: "test",
status: 401, status: 401,
}) })
} }

View File

@@ -869,8 +869,8 @@ export const useCalendar = defineStore("calendar", () => {
const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id) const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
baseEvents.value[eventIndex] = res baseEvents.value[eventIndex] = res
} catch (err) {
console.log(err) return res
} finally { } finally {
abortController = null abortController = null
isUpdatingEvent.value = false isUpdatingEvent.value = false

View File

@@ -247,17 +247,13 @@ export const useCalendarEvents = defineStore("calendar-events", () => {
abortController = new AbortController() abortController = new AbortController()
isUpdatingEvent.value = true isUpdatingEvent.value = true
try { const res = await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: "PATCH", body: { event : eventSkeleton.value, calendarId: activeCalendar?.id }, signal: abortController.signal })
const res = await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: "PATCH", body: { event : eventSkeleton.value, calendarId: activeCalendar?.id }, signal: abortController.signal })
const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id) const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
baseEvents.value[eventIndex] = res baseEvents.value[eventIndex] = res
} catch (err) {
console.log(err) abortController = null
} finally { isUpdatingEvent.value = false
abortController = null
isUpdatingEvent.value = false
}
} }
async function deleteEventFromSkeleton() { async function deleteEventFromSkeleton() {