From 328f9718d751cc70c5463c3ead9be876d4b359b9 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Martin <pierre-alexandre.martin@imt-atlantique.net> Date: Tue, 18 Feb 2025 15:08:36 +0100 Subject: [PATCH] =?UTF-8?q?Connexion=20=C3=A0=20l'API=20et=20d'ajout=20des?= =?UTF-8?q?=20pages=20de=20consultation=20et=20d'=C3=A9dition=20d'exercice?= =?UTF-8?q?s=20+=20Am=C3=A9lioration=20de=20la=20page=20Parcours=20+=20doc?= =?UTF-8?q?ker-compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +- app.config.ts | 11 +- assets/css/colors.css | 89 +- components/AccountPlaceholder.vue | 2 +- components/AppFooter.vue | 2 +- components/AppHeader.vue | 2 +- components/ExerciceItem.vue | 40 + components/Modal/Confirmation.vue | 24 + components/Parcours/Details.vue | 100 ++ .../Parcours/Exercices.vue | 173 ++- components/exerciceItem.vue | 45 - docker-compose.yaml | 31 + dockerfile | 3 +- nuxt.config.ts | 3 +- package-lock.json | 1317 +++++++++++------ package.json | 19 +- package.json.md | 12 + pages/exercices/[id].vue | 3 - pages/exercise/[id].vue | 149 ++ pages/exercises.vue | 211 +++ pages/index.vue | 2 +- pages/login.vue | 42 +- pages/parcours.vue | 5 +- plugins/api.ts | 20 + repository/model/IExercise.ts | 15 + repository/model/IUser.ts | 4 + repository/model/LoginBody.ts | 4 + repository/module/exercise.ts | 105 ++ repository/module/user.ts | 21 + server/api/auth/login.post.ts | 20 + server/api/exercise/.delete.ts | 27 + server/api/exercise/.get.ts | 24 + server/api/exercise/.patch.ts | 45 + server/api/exercise/.post.ts | 45 + .../api/exercise/fetch-sub-categories.get.ts | 29 + types/Exercice.ts | 16 - types/Exercise.ts | 33 + types/ResponseBody.ts | 4 + types/enum/ExerciseCategory.ts | 40 +- utils/mapper/ExerciseMapper.test.ts | 105 ++ utils/mapper/ExerciseMapper.ts | 35 + utils/mapper/UserMapper.ts | 2 +- 42 files changed, 2220 insertions(+), 696 deletions(-) create mode 100644 components/ExerciceItem.vue create mode 100644 components/Modal/Confirmation.vue create mode 100644 components/Parcours/Details.vue rename pages/exercices.vue => components/Parcours/Exercices.vue (55%) delete mode 100644 components/exerciceItem.vue create mode 100644 docker-compose.yaml create mode 100644 package.json.md delete mode 100644 pages/exercices/[id].vue create mode 100644 pages/exercise/[id].vue create mode 100644 pages/exercises.vue create mode 100644 plugins/api.ts create mode 100644 repository/model/IExercise.ts create mode 100644 repository/model/IUser.ts create mode 100644 repository/model/LoginBody.ts create mode 100644 repository/module/exercise.ts create mode 100644 repository/module/user.ts create mode 100644 server/api/auth/login.post.ts create mode 100644 server/api/exercise/.delete.ts create mode 100644 server/api/exercise/.get.ts create mode 100644 server/api/exercise/.patch.ts create mode 100644 server/api/exercise/.post.ts create mode 100644 server/api/exercise/fetch-sub-categories.get.ts delete mode 100644 types/Exercice.ts create mode 100644 types/Exercise.ts create mode 100644 types/ResponseBody.ts create mode 100644 utils/mapper/ExerciseMapper.test.ts create mode 100644 utils/mapper/ExerciseMapper.ts diff --git a/README.md b/README.md index 10ced06..1f31573 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,24 @@ Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduct Make sure to install dependencies: ```bash -# npm npm install ``` +## Environment Variables + +Create a `.env` file at the root of the project with the following variables: + +```bash +# either latest or dev; used for docker-compose deployment (default is latest) +TAG=latest +# depends of your API configuration (default is 3d) +JWT_EXPIRES=3d +# depends of your API url (default is http://localhost:3001) +API_BASE_URL=http://localhost:3001 +# generate a random string for the secret key (https://www.random.org/strings/) +SECRET_KEY=AZERTY +``` + ## Development Server Be sure there are no new dependency updates: @@ -23,31 +37,34 @@ npm install Start the development server on `http://localhost:3000`: ```bash -# npm npm run dev ``` ## Production -Build the application for production: +Locally preview production build: ```bash -# npm npm run build +npm run preview ``` -Locally preview production build: +With docker for a local build with the API: ```bash -# npm -npm run preview +# Start the API +docker pull gitlab-registry.imt-atlantique.fr/3spa/api:latest +docker run -d -p 3001:3000 --env-file .env --name 3spa-api gitlab-registry.imt-atlantique.fr/3spa/api:latest + +# Start the app +docker build -t 3spa_web-admin . +docker run -d -p 3000:3000 --env-file .env --name 3spa_web-admin 3spa_web-admin ``` -With docker +With docker-compose ```bash -docker build -t 3spa . -docker run --rm -it -p 3000:3000 --env-file .env --name 3spa 3spa +docker-compose up -d ``` Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/app.config.ts b/app.config.ts index d1630e5..c7a91d6 100644 --- a/app.config.ts +++ b/app.config.ts @@ -30,16 +30,17 @@ export default defineAppConfig({ } }, toaster: { - position: 'bottom-right' as const, + position: 'top-right' as const, expand: true, duration: 5000 }, ui: { colors: { - primary: 'primary', - secondary: 'secondary', - success: 'success', - error: 'error', + primary: '3spa_primary', + secondary: '3spa_secondary', + success: '3spa_success', + error: '3spa_error', + accent: 'accent', } } }) diff --git a/assets/css/colors.css b/assets/css/colors.css index 4481e0c..4cea775 100644 --- a/assets/css/colors.css +++ b/assets/css/colors.css @@ -4,29 +4,30 @@ @theme { /*created with https://www.tailwindshades.com/*/ - --color-primary-50: #9DF5D8; - --color-primary-100: #8BF3D0; - --color-primary-200: #66F0C1; - --color-primary-300: #41ECB3; - --color-primary-400: #1CE8A4; - --color-primary-500: #14C78B; - --color-primary-600: #10A271; - --color-primary-700: #0D7D57; - --color-primary-800: #09583D; - --color-primary-900: #053323; - --color-primary-950: #032016; + /*FIXME: add issue in nuxt/ui git to ask if there is a bug while using color-primary-X*/ + --color-3spa_primary-50: #9DF5D8; + --color-3spa_primary-100: #8BF3D0; + --color-3spa_primary-200: #66F0C1; + --color-3spa_primary-300: #41ECB3; + --color-3spa_primary-400: #1CE8A4; + --color-3spa_primary-500: #14C78B; + --color-3spa_primary-600: #10A271; + --color-3spa_primary-700: #0D7D57; + --color-3spa_primary-800: #09583D; + --color-3spa_primary-900: #053323; + --color-3spa_primary-950: #032016; - --color-secondary-50: light-dark(#427684, #FDFEF8); - --color-secondary-100: light-dark(#3E6F7C, #FDFDF6); - --color-secondary-200: light-dark(#36606B, #FBFCF2); - --color-secondary-300: light-dark(#2D505A, #FAFCEE); - --color-secondary-400: light-dark(#254149, #F9FBE9); - --color-secondary-500: light-dark(#1C3238, #F8FAE5); - --color-secondary-600: light-dark(#192C31, #F7F9E1); - --color-secondary-700: light-dark(#15262A, #F6F8DC); - --color-secondary-800: light-dark(#122024, #F5F8D8); - --color-secondary-900: light-dark(#0E1A1D, #F3F7D4); - --color-secondary-950: light-dark(#0D1719, #F3F6D2); + --color-3spa_secondary-50: light-dark(#427684, #FDFEF8); + --color-3spa_secondary-100: light-dark(#3E6F7C, #FDFDF6); + --color-3spa_secondary-200: light-dark(#36606B, #FBFCF2); + --color-3spa_secondary-300: light-dark(#2D505A, #FAFCEE); + --color-3spa_secondary-400: light-dark(#254149, #F9FBE9); + --color-3spa_secondary-500: light-dark(#1C3238, #F8FAE5); + --color-3spa_secondary-600: light-dark(#192C31, #F7F9E1); + --color-3spa_secondary-700: light-dark(#15262A, #F6F8DC); + --color-3spa_secondary-800: light-dark(#122024, #F5F8D8); + --color-3spa_secondary-900: light-dark(#0E1A1D, #F3F7D4); + --color-3spa_secondary-950: light-dark(#0D1719, #F3F6D2); --color-background-50: light-dark(#FDFEF8, #427684); --color-background-100: light-dark(#FDFDF6, #3E6F7C); @@ -52,27 +53,27 @@ --color-accent-900: #8F1B00; --color-accent-950: #7A1700; - --color-error-50: #FFB8B8; - --color-error-100: #FFA3A3; - --color-error-200: #FF7A7A; - --color-error-300: #FF5252; - --color-error-400: #FF2929; - --color-error-500: #FF0000; - --color-error-600: #D60000; - --color-error-700: #AD0000; - --color-error-800: #850000; - --color-error-900: #5C0000; - --color-error-950: #470000; + --color-3spa_error-50: #FFB8B8; + --color-3spa_error-100: #FFA3A3; + --color-3spa_error-200: #FF7A7A; + --color-3spa_error-300: #FF5252; + --color-3spa_error-400: #FF2929; + --color-3spa_error-500: #FF0000; + --color-3spa_error-600: #D60000; + --color-3spa_error-700: #AD0000; + --color-3spa_error-800: #850000; + --color-3spa_error-900: #5C0000; + --color-3spa_error-950: #470000; - --color-success-50: #07FF07; - --color-success-100: #00FC00; - --color-success-200: #00E700; - --color-success-300: #00D300; - --color-success-400: #00BE00; - --color-success-500: #00AA00; - --color-success-600: #009600; - --color-success-700: #008100; - --color-success-800: #006D00; - --color-success-900: #005800; - --color-success-950: #004E00; + --color-3spa_success-50: #07FF07; + --color-3spa_success-100: #00FC00; + --color-3spa_success-200: #00E700; + --color-3spa_success-300: #00D300; + --color-3spa_success-400: #00BE00; + --color-3spa_success-500: #00AA00; + --color-3spa_success-600: #009600; + --color-3spa_success-700: #008100; + --color-3spa_success-800: #006D00; + --color-3spa_success-900: #005800; + --color-3spa_success-950: #004E00; } \ No newline at end of file diff --git a/components/AccountPlaceholder.vue b/components/AccountPlaceholder.vue index 5168968..6b61f92 100644 --- a/components/AccountPlaceholder.vue +++ b/components/AccountPlaceholder.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import userData from '../data/mock/user.json'; +import userData from '~/data/mock/user.json'; import {UserMapper} from "~/utils/mapper/UserMapper"; const user = ref<User>(UserMapper.from(userData)); diff --git a/components/AppFooter.vue b/components/AppFooter.vue index bdd9e88..d48e5dd 100644 --- a/components/AppFooter.vue +++ b/components/AppFooter.vue @@ -23,6 +23,6 @@ footer { footer > p { margin: 1vh; - color: var(--color-secondary-500); + color: var(--color-3spa_secondary-500); } </style> \ No newline at end of file diff --git a/components/AppHeader.vue b/components/AppHeader.vue index a1ee0be..15d612b 100644 --- a/components/AppHeader.vue +++ b/components/AppHeader.vue @@ -43,7 +43,7 @@ header { .title { margin: 1vh; - color: var(--color-primary-500); + color: var(--color-3spa_primary-500); } header span { diff --git a/components/ExerciceItem.vue b/components/ExerciceItem.vue new file mode 100644 index 0000000..8134feb --- /dev/null +++ b/components/ExerciceItem.vue @@ -0,0 +1,40 @@ +<script setup lang="ts"> +import type {Exercise} from "~/types/Exercise"; +import {getExerciseCategoryIcon, translateExerciseCategoryToFrench} from "~/types/enum/ExerciseCategory"; + +const props = defineProps<{ + exercise: Exercise +}>() +</script> + +<template> + <div class="exercise-item" :class="{ 'exercise-selected' : exercise.selected }"> + <UIcon :name="getExerciseCategoryIcon(exercise.category)" class="h-9 w-9 m-1.5"/> + <div class="grid gap-2 w-full"> + <p>{{ props.exercise.name }}</p> + <small> + {{ translateExerciseCategoryToFrench(props.exercise.category) }} + {{ props.exercise.subCategory ? '> ' + props.exercise.subCategory : '' }} + </small> + </div> + </div> +</template> + +<style> +div.exercise-item { + transition: 0.4s; +} + +div.exercise-item:hover { + background-color: var(--color-background-800); + border-radius: 25px; +} + +.exercise-item > span { + color: var(--color-accent-500) +} + +.exercise-selected > span { + color: var(--color-3spa_success-500) +} +</style> \ No newline at end of file diff --git a/components/Modal/Confirmation.vue b/components/Modal/Confirmation.vue new file mode 100644 index 0000000..5f18302 --- /dev/null +++ b/components/Modal/Confirmation.vue @@ -0,0 +1,24 @@ +<script setup lang="ts"> +defineProps<{ + cancelIcon?: string, + confirmIcon?: string, +}>(); + +const modal = useModal(); +const emit = defineEmits(['success']); + +function onSuccess() { + emit('success'); +} +</script> + +<template> + <UModal> + <template #footer> + <div class="flex gap-2 justify-center w-full"> + <UButton :icon="cancelIcon" label="Non" color="secondary" @click="modal.close()"/> + <UButton :icon="confirmIcon" label="Confirmer" @click="onSuccess"/> + </div> + </template> + </UModal> +</template> diff --git a/components/Parcours/Details.vue b/components/Parcours/Details.vue new file mode 100644 index 0000000..8a456cf --- /dev/null +++ b/components/Parcours/Details.vue @@ -0,0 +1,100 @@ +<script setup lang="ts"> +import type {PointTuple} from "leaflet"; +import * as v from 'valibot' +import type {FormSubmitEvent} from '@nuxt/ui' + +const toast = useToast() + +const schema = v.object({ + parcoursTitle: v.pipe(v.string(), v.nonEmpty('Titre obligatoire')), + localization: v.pipe(v.string(), v.nonEmpty('Localisation obligatoire')), + duration: v.pipe(v.string(), v.isoTime('Invalid time format')) +}) + +type Schema = v.InferOutput<typeof schema> + +const map = ref(null) as any; +const state = reactive({ + parcoursTitle: '', + localization: '', + duration: '00:30' +}) +// TODO : calculate these datas from the data +const zoom = ref(16.5) +const maxBounds = [[47.283, -1.53], [47.29, -1.52]] +const center: PointTuple = [47.287, -1.524] + +async function onSubmit(event: FormSubmitEvent<Schema>) { + toast.add({title: 'Success', description: 'The form has been submitted.', color: 'success'}) + state.parcoursTitle = ''; + state.localization = ''; + state.duration = '00:30' + // TODO : Send the data to the API + // $api.parcours.createParcours(state) + console.log(event.data) +} +</script> + +<template> + <UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4 grid-container" @submit="onSubmit"> + <UFormField label="Titre du parcours" name="parcoursTitle" class="col-span-2"> + <UInput + v-model="state.parcoursTitle" placeholder="Titre du parcours" class="w-full" + style="margin-right: 20px"/> + </UFormField> + + <LMap + ref="map" + class="row-span-3" + style="height: auto;" + :zoom="zoom" + :min-zoom="zoom" + :max-zoom="zoom" + :center="center" + :max-bounds="maxBounds" + :use-global-leaflet="true" + :options="{ zoomSnap: 0.75 }" + > + <LTileLayer + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + attribution="&copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a>" + layer-type="base" + name="OpenStreetMap" + /> + </LMap> + + <UFormField label="Localisation" name="localization"> + <UInput + v-model="state.localization" placeholder="Localisation" class="w-full" + style="margin-right: 20px"/> + </UFormField> + + <UFormField label="Durée (hh:mm)" name="duration"> + <UInput v-model="state.duration" type="time" class="w-full" style="margin-right: 20px"/> + </UFormField> + + <div class="grid-item col-span-2"> + <a href="https://github.com/kutlugsahin/vue-smooth-dnd?tab=readme-ov-file">Implements drag and drop from + this library</a> + </div> + + <UButton type="submit" class="col-span-3 w-fit justify-self-center"> + Enregistrer le parcours + </UButton> + </UForm> +</template> + +<style> +.grid-container { + display: grid; + grid-template-columns: 3fr 1fr 4fr; + grid-template-rows: 1fr 1fr 6fr auto; + height: 65vh; + gap: 1px; +} + +.grid-item { + background-color: lightgray; + border: 1px solid gray; +} +</style> \ No newline at end of file diff --git a/pages/exercices.vue b/components/Parcours/Exercices.vue similarity index 55% rename from pages/exercices.vue rename to components/Parcours/Exercices.vue index e27fb6a..e53d45d 100644 --- a/pages/exercices.vue +++ b/components/Parcours/Exercices.vue @@ -1,29 +1,29 @@ <script setup lang="ts"> import L, {type LeafletMouseEvent, Marker, type PointTuple} from 'leaflet'; import 'leaflet.markercluster'; -import myData from '../data/mock/exercices.json'; -import type Exercise from "~/types/Exercice"; +import myData from '~/data/mock/exercices.json'; +import type {Exercise} from "~/types/Exercise"; +const {$api} = useNuxtApp(); const map = ref(null) as any; const inCreation = ref(false); -const exercises = ref(myData); +const exercises = await $api.exercises.getExercises(); +const exerciseSearch = ref(''); +const selectedExercises: Exercise[] = []; // TODO : Move to a separate file interface MarkerCluster { markerCluster: L.LayerGroup; - // markerCluster: L.Map; markers: Marker[]; - // options?: L.MarkerClusterGroupOptions; } -let _markerCluster: MarkerCluster; -let newMarkerCluster: MarkerCluster; +let markerCluster: MarkerCluster; // DATA const zoom = ref(17) const chantrerie: PointTuple = [47.287, -1.524] -const locations2: any = myData.map((exo) => { +const locations: any = myData.map((exo) => { return { name: exo.id, lat: exo.lat, @@ -38,30 +38,7 @@ const locations2: any = myData.map((exo) => { } }) -const locations = [ - { - name: 'Chantrerie', - lat: 47.287, - lng: -1.524, - options: { - icon: L.icon({ - iconUrl: '/svg/pin.svg', - iconSize: [30, 30], - }) - }, - popup: 'Parc de la chantrerie' - }, { - name: 'Chantrerie', - lat: 47.287, - lng: -1.523, - options: { - icon: L.icon({ - iconUrl: '/svg/pin.svg', - iconSize: [30, 30], - }) - } - } -] +// TODO & FIXME : menu contextuel à revoir const items = ref([ [ { @@ -94,17 +71,10 @@ const items = ref([ // Functions async function onMapReady() { - _markerCluster = await useLMarkerCluster({ + markerCluster = await useLMarkerCluster({ leafletObject: map?.value?.leafletObject, markers: locations }); - newMarkerCluster = await useLMarkerCluster({ - leafletObject: map?.value?.leafletObject, - markers: locations2 - }); - - // newMarkerCluster = L.markerClusterGroup(); - // map.addLayer(newMarkerCluster); map.value.leafletObject.on('moveend', () => { // ask api for new markers in bounds @@ -114,7 +84,7 @@ async function onMapReady() { function deleteMarker(): void { if (!inCreation.value) return; - newMarkerCluster.markerCluster.clearLayers(); + markerCluster.markerCluster.clearLayers(); inCreation.value = false; } @@ -129,9 +99,10 @@ function MapEvents(e: LeafletMouseEvent): void { iconUrl: '/svg/pinadd.svg', iconSize: [30, 30], })); - newMarkerCluster.markerCluster.addLayer(newMarker); + markerCluster.markerCluster.addLayer(newMarker); } +// TODO : changement d'affichage des clusters const _cluster_visible = (cluster: any) => L.divIcon({ html: `<div style="background-color: red;">${cluster.getChildCount()}</div>`, className: 'marker-cluster', @@ -140,7 +111,7 @@ const _cluster_visible = (cluster: any) => L.divIcon({ // FIXME : still a lot of works around this function hooverMarker(exo: Exercise): void { - newMarkerCluster.markers.find((marker: Marker) => { + markerCluster.markers.find((marker: Marker) => { if (marker.getLatLng().lat === exo.lat && marker.getLatLng().lng === exo.lng) { if (map.value.leafletObject.hasLayer(marker)) { // Not in a cluster (ie marker is visible on map) @@ -148,35 +119,35 @@ function hooverMarker(exo: Exercise): void { iconUrl: '/svg/pin.svg', iconSize: [45, 45], })); - } else { - // In a cluster (ie marker is not visible on map) - map.value.leafletObject.eachLayer((layer: any) => { - if (layer instanceof L.FeatureGroup && layer.hasLayer(marker)) { - (layer as any).options = { - iconCreateFunction: (cluster: any) => { - return L.divIcon({ - html: `<div style="background-color: red;">${cluster.getChildCount()}</div>`, - className: 'marker-cluster', - iconSize: L.point(40, 40) - }); - } - } - // console.log((layer as any)); - // console.log((layer as any)._markerCluster); - // (newMarkerCluster as any).refreshClusters(); - // (layer as any).setIcon(cluster_visible); - // map.value.leafletObject.removeLayer(layer); - // map.value.leafletObject.addLayer(layer); - // (map.value.leafletObject as L.LayerGroup) - } - }); + // } else { + // In a cluster (ie marker is not visible on map) + // map.value.leafletObject.eachLayer((layer: any) => { + // if (layer instanceof L.FeatureGroup && layer.hasLayer(marker)) { + // (layer as any).options = { + // iconCreateFunction: (cluster: any) => { + // return L.divIcon({ + // html: `<div style="background-color: red;">${cluster.getChildCount()}</div>`, + // className: 'marker-cluster', + // iconSize: L.point(40, 40) + // }); + // } + // } + // console.log((layer as any)); + // console.log((layer as any)._markerCluster); + // (newMarkerCluster as any).refreshClusters(); + // (layer as any).setIcon(cluster_visible); + // map.value.leafletObject.removeLayer(layer); + // map.value.leafletObject.addLayer(layer); + // (map.value.leafletObject as L.LayerGroup) + // } + // }); } } }); } function leaveMarker(exo: Exercise): void { - newMarkerCluster.markers.find((marker: any) => { + markerCluster.markers.find((marker: any) => { if (marker.getLatLng().lat === exo.lat && marker.getLatLng().lng === exo.lng) { marker.setIcon(L.icon({ iconUrl: '/svg/pin.svg', @@ -185,34 +156,59 @@ function leaveMarker(exo: Exercise): void { } }); } + +function filterExercise(): Exercise[] { + return exercises.filter((exo: Exercise) => { + return exo.name.toLowerCase().includes(exerciseSearch.value.toLowerCase()); + }); +} + +function onExerciceClick(exo: Exercise): void { + if (exo.selected) { + selectedExercises.splice(selectedExercises.indexOf(exo), 1); + document.getElementById(`exercise-item-${exo.id}`)?.classList.remove('exercise-selected'); + exercises.find((exercise: Exercise) => { + if (exercise.id === exo.id) { + exercise.selected = false; + } + }); + } else { + selectedExercises.push(exo); + document.getElementById(`exercise-item-${exo.id}`)?.classList.add('exercise-selected'); + exercises.find((exercise: Exercise) => { + if (exercise.id === exo.id) { + exercise.selected = true; + } + }) + } +} </script> <template> <div id="exercices"> <UContextMenu :items="items" :ui="{ content: 'w-48' }"> - <div> - <LMap - ref="map" - :zoom="zoom" - :min-zoom="15" - :center="chantrerie" - :use-global-leaflet="true" - :options="{ zoomSnap: 0.75 }" - @ready="onMapReady" - @click="MapEvents" - > - <LTileLayer - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" - attribution="&copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a>" - layer-type="base" - name="OpenStreetMap" - /> - </LMap> - </div> + <LMap + ref="map" + :zoom="zoom" + :min-zoom="15" + :center="chantrerie" + :use-global-leaflet="true" + :options="{ zoomSnap: 0.75 }" + @ready="onMapReady" + @click="MapEvents" + > + <LTileLayer + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + attribution="&copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a>" + layer-type="base" + name="OpenStreetMap" + /> + </LMap> </UContextMenu> <div id="side" class="flex flex-col w-full h-full p-2 overflow-hidden"> <UInput id="search-input" + v-model="exerciseSearch" icon="material-symbols:search" size="md" variant="outline" placeholder="" :ui="{ base: 'peer' }"> @@ -226,9 +222,12 @@ function leaveMarker(exo: Exercise): void { </UInput> <USeparator class="m-1 w-full"/> <div id="exercices-list" class="flex flex-col w-full h-full overflow-auto gap-1"> - <exerciceItem - v-for="exercise in exercises" :key="exercise.id" :exercise="exercise as Exercise" + <ExerciceItem + v-for="exercise in filterExercise()" :id="'exercise-item-'+exercise.id" + :key="exercise.id" ref="list" + :exercise="exercise as Exercise" class="skeletton gap-4" + @click="onExerciceClick(exercise)" @mouseover="hooverMarker(exercise as Exercise)" @mouseleave="leaveMarker(exercise as Exercise)"/> <div class="skeletton gap-4"> <USkeleton class="h-12 w-12 rounded-full"/> diff --git a/components/exerciceItem.vue b/components/exerciceItem.vue deleted file mode 100644 index bcacfe1..0000000 --- a/components/exerciceItem.vue +++ /dev/null @@ -1,45 +0,0 @@ -<script setup lang="ts"> -import type Exercise from '../types/Exercice.js'; - -function getIcon() { - switch (props.exercise.category) { - case ExerciseCategory.RENFORCEMENT: - return 'material-symbols:exercise-outline'; - case ExerciseCategory.CARDIO: - return 'material-symbols:cardiology-outline'; - case ExerciseCategory.STRETCHING: - return 'material-symbols:transfer-within-a-station'; - default: - return 'material-symbols:question-mark'; - } -} - -const props = defineProps<{ - exercise: Exercise -}>() -</script> - -<template> - <div class="exercise-item"> - <UIcon :name="getIcon()" class="h-9 w-9 m-1.5" style="color: var(--color-accent-500)"/> - <div class="grid gap-2 w-full"> - <p>{{ props.exercise.name }}</p> - <small> - {{ props.exercise.category }} - {{ props.exercise.subCategory ? '> ' + props.exercise.subCategory : '' }} - </small> - </div> - </div> -</template> - -<style> -div.exercise-item { - transition: 0.4s; -} - -div.exercise-item:hover { - transform: scale(1.05); - background-color: var(--color-background-800); - border-radius: 25px; -} -</style> \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0281243 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,31 @@ +services: + api: + image: gitlab-registry.imt-atlantique.fr/3spa/api:${TAG} + container_name: api + restart: always + env_file: .env + ports: + - "3001:3000" + volumes: + - api_data:/app/data + networks: + - app_network + + web-admin: + image: gitlab-registry.imt-atlantique.fr/3spa/web-admin:${TAG} + container_name: web-admin + restart: always + env_file: .env + ports: + - "3000:3000" + networks: + - app_network + depends_on: + - api + +volumes: + api_data: + +networks: + app_network: + driver: bridge diff --git a/dockerfile b/dockerfile index 608cd8e..e1f42b0 100644 --- a/dockerfile +++ b/dockerfile @@ -21,7 +21,8 @@ RUN npm run build # Run FROM base -ENV PORT=$PORT +# ENV PORT=$PORT +EXPOSE 3000 ENV NODE_ENV=production COPY --from=build /src/.output /src/.output diff --git a/nuxt.config.ts b/nuxt.config.ts index ff2b7d6..f7b4a86 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -19,10 +19,9 @@ export default defineNuxtConfig({ /** Les variables d'environnement */ runtimeConfig: { - apiSecret: '123', // Keys within public are also exposed client-side public: { - apiBase: '/api' + apiBase: process.env.API_BASE_URL } }, diff --git a/package-lock.json b/package-lock.json index b7b6f7e..d65a610 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,21 +7,22 @@ "name": "nuxt-app", "hasInstallScript": true, "dependencies": { - "@iconify-json/material-symbols": "^1.2.14", - "@nuxt/eslint": "^1.0.0", - "@nuxt/ui": "^3.0.0-alpha.11", + "@nuxt/eslint": "^1.0.1", + "@nuxt/ui": "^3.0.0-alpha.13", "@nuxtjs/leaflet": "^1.2.6", - "eslint": "^9.19.0", + "eslint": "^9.20.1", "leaflet.markercluster": "^1.5.3", "nuxt": "^3.15.4", - "valibot": "^1.0.0-beta.15", + "valibot": "^1.0.0-rc.1", "vue": "latest", "vue-router": "latest" }, "devDependencies": { + "@iconify-json/lucide": "^1.2.26", + "@iconify-json/material-symbols": "^1.2.14", "@nuxt/test-utils": "^3.15.4", "@vue/test-utils": "^2.4.6", - "happy-dom": "^16.8.1", + "happy-dom": "^17.1.0", "typescript": "^5.7.3", "vitest": "^3.0.5" } @@ -632,9 +633,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", "cpu": [ "ppc64" ], @@ -648,9 +649,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", "cpu": [ "arm" ], @@ -664,9 +665,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", "cpu": [ "arm64" ], @@ -680,9 +681,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", "cpu": [ "x64" ], @@ -696,9 +697,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "cpu": [ "arm64" ], @@ -712,9 +713,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", "cpu": [ "x64" ], @@ -728,9 +729,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", "cpu": [ "arm64" ], @@ -744,9 +745,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", "cpu": [ "x64" ], @@ -760,9 +761,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", "cpu": [ "arm" ], @@ -776,9 +777,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", "cpu": [ "arm64" ], @@ -792,9 +793,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", "cpu": [ "ia32" ], @@ -808,9 +809,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", "cpu": [ "loong64" ], @@ -824,9 +825,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", "cpu": [ "mips64el" ], @@ -840,9 +841,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", "cpu": [ "ppc64" ], @@ -856,9 +857,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", "cpu": [ "riscv64" ], @@ -872,9 +873,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", "cpu": [ "s390x" ], @@ -888,9 +889,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "cpu": [ "x64" ], @@ -904,9 +905,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", "cpu": [ "arm64" ], @@ -920,9 +921,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", "cpu": [ "x64" ], @@ -936,9 +937,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", "cpu": [ "arm64" ], @@ -952,9 +953,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", "cpu": [ "x64" ], @@ -968,9 +969,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", "cpu": [ "x64" ], @@ -984,9 +985,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", "cpu": [ "arm64" ], @@ -1000,9 +1001,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", "cpu": [ "ia32" ], @@ -1016,9 +1017,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", "cpu": [ "x64" ], @@ -1071,9 +1072,9 @@ } }, "node_modules/@eslint/compat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.5.tgz", - "integrity": "sha512-5iuG/StT+7OfvhoBHPlmxkPA9om6aDUFgmD4+mWKAGsYt4vCe8rypneG03AuseyRHBmcCLXQtIH5S26tIoggLg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.6.tgz", + "integrity": "sha512-k7HNCqApoDHM6XzT30zGoETj+D+uUcZUb+IVAJmar3u6bvHf7hhHJcWx09QHj4/a2qrKZMWU0E16tvkiAdv06Q==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1235,9 +1236,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", + "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1388,10 +1389,21 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.26", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.26.tgz", + "integrity": "sha512-arD/8mK0lRxFY2LgLf345NhWVWiOtV8sOxJuLnq4QRz3frMiOwVwGxEgp5Xe/bRGzxO2CxxCBok0bPRpCkYZQQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify-json/material-symbols": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.14.tgz", "integrity": "sha512-S0AAFFQPVr8Dkrprspz/otNjxdD3rJRXDGZjbO8a8zn8ZR5mO8jAF81lVoTfUWxPH6SCtH2lK1JQGXHGPxld7g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@iconify/types": "*" @@ -1954,24 +1966,24 @@ } }, "node_modules/@nuxt/eslint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nuxt/eslint/-/eslint-1.0.0.tgz", - "integrity": "sha512-RtVvRM+rv9q4x9nZVHkVo7X0gRvockZm6b5EYshO0A0qjughD3fj7N1sSYxvyO9n8IBxrnWT5jHa+2txdzuthg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nuxt/eslint/-/eslint-1.0.1.tgz", + "integrity": "sha512-Bh6M9Tjf4jyIyfobu/ekW6YHMCsWpvZHuZN/6tRVKwXgAL6fiJG6tHm3uYJ5ILcVTdp6tz/j/sW8dgP+ijHLDQ==", "license": "MIT", "dependencies": { "@eslint/config-inspector": "^1.0.0", "@nuxt/devtools-kit": "^1.7.0", - "@nuxt/eslint-config": "1.0.0", - "@nuxt/eslint-plugin": "1.0.0", + "@nuxt/eslint-config": "1.0.1", + "@nuxt/eslint-plugin": "1.0.1", "@nuxt/kit": "^3.15.4", "chokidar": "^4.0.3", - "eslint-flat-config-utils": "^2.0.0", + "eslint-flat-config-utils": "^2.0.1", "eslint-typegen": "^1.0.0", "find-up": "^7.0.0", "get-port-please": "^3.1.2", "mlly": "^1.7.4", "pathe": "^2.0.2", - "unimport": "^4.0.0" + "unimport": "^4.1.0" }, "peerDependencies": { "eslint": "^9.0.0", @@ -1988,20 +2000,20 @@ } }, "node_modules/@nuxt/eslint-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nuxt/eslint-config/-/eslint-config-1.0.0.tgz", - "integrity": "sha512-Bxvx6y68WqLkubKv9zDP7mdl82ljO7Zmqi13RWveGTvFt61BwP4bQIF10s8r1rfE+2svDN8d0L9eIDoDHtHZQg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nuxt/eslint-config/-/eslint-config-1.0.1.tgz", + "integrity": "sha512-N1sSvtrCWehBHOhB4VBVDxZyDxkXv5b4QN+NQC0suCGR4VekZj8mciaepWFTb66gNrpA/IHYfBId308Na++FwA==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^1.0.0", "@clack/prompts": "^0.9.1", "@eslint/js": "^9.19.0", - "@nuxt/eslint-plugin": "1.0.0", + "@nuxt/eslint-plugin": "1.0.1", "@stylistic/eslint-plugin": "^3.0.1", - "@typescript-eslint/eslint-plugin": "^8.22.0", - "@typescript-eslint/parser": "^8.22.0", + "@typescript-eslint/eslint-plugin": "^8.23.0", + "@typescript-eslint/parser": "^8.23.0", "eslint-config-flat-gitignore": "^2.0.0", - "eslint-flat-config-utils": "^2.0.0", + "eslint-flat-config-utils": "^2.0.1", "eslint-merge-processors": "^1.0.0", "eslint-plugin-import-x": "^4.6.1", "eslint-plugin-jsdoc": "^50.6.3", @@ -2041,13 +2053,13 @@ } }, "node_modules/@nuxt/eslint-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nuxt/eslint-plugin/-/eslint-plugin-1.0.0.tgz", - "integrity": "sha512-mYkq6V3xCVwnJxiwqTYfEe3HYV/Nxayes9cqY5maijSMJSCSI8l73FYWal2HFvsSoqilYhN4EfgzCHPhyWHqQQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nuxt/eslint-plugin/-/eslint-plugin-1.0.1.tgz", + "integrity": "sha512-pqad64otqRmYTnaUSD1Jc57iWHACdWebD6UUb1hLVl/ML1B4yFxn5xq8kXRjzQsSKX3yMs7dFTYS0ygw04n+SA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.22.0", - "@typescript-eslint/utils": "^8.22.0" + "@typescript-eslint/types": "^8.23.0", + "@typescript-eslint/utils": "^8.23.0" }, "peerDependencies": { "eslint": "^9.0.0" @@ -2082,12 +2094,11 @@ } }, "node_modules/@nuxt/eslint/node_modules/unimport": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.0.0.tgz", - "integrity": "sha512-FH+yZ36YaVlh0ZjHesP20Q4uL+wL0EqTNxDZcUupsIn6WRYXZAbIYEMDLTaLBpkNVzFpqZXS+am51/HR3ANUNw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.1.0.tgz", + "integrity": "sha512-y5ZYDG+j7IB45+Y6CIkWIKou4E1JFigCUw6vI+h15HdYAKmT0oQWcawnxXuwJG8srJyXhIZuWz5uXB1MQ/ARZw==", "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", @@ -2100,10 +2111,11 @@ "pkg-types": "^1.3.1", "scule": "^1.3.0", "strip-literal": "^3.0.0", - "unplugin": "^2.1.2" + "unplugin": "^2.1.2", + "unplugin-utils": "^0.2.3" }, "engines": { - "node": ">=18.12.0" + "node": ">=18.20.6" } }, "node_modules/@nuxt/fonts": { @@ -2393,29 +2405,28 @@ } }, "node_modules/@nuxt/ui": { - "version": "3.0.0-alpha.11", - "resolved": "https://registry.npmjs.org/@nuxt/ui/-/ui-3.0.0-alpha.11.tgz", - "integrity": "sha512-J/9XhhRD6ooNUk192KcWEAioBDUsVCaLfDSUbxWEJvVKtT74fY8bXZZ38nTzgptF8Evg2dxIugFCoJXLu5xgEQ==", + "version": "3.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@nuxt/ui/-/ui-3.0.0-alpha.13.tgz", + "integrity": "sha512-RjCR5P1jdgsmVPgaIsaZaTT20r+EN3Qt3cQlu5yiE/9VhrF5YZWfxcNKIGdDqgRObRKgpV0GlaxYVZiDu9wAmQ==", "license": "MIT", "dependencies": { "@iconify/vue": "^4.3.0", - "@internationalized/date": "^3.6.0", + "@internationalized/date": "^3.7.0", "@internationalized/number": "^3.6.0", - "@nuxt/devtools-kit": "^1.7.0", + "@nuxt/devtools-kit": "^2.1.0", "@nuxt/fonts": "^0.10.3", "@nuxt/icon": "^1.10.3", - "@nuxt/kit": "^3.15.1", - "@nuxt/schema": "^3.15.1", + "@nuxt/kit": "^3.15.4", + "@nuxt/schema": "^3.15.4", "@nuxtjs/color-mode": "^3.5.2", - "@tailwindcss/postcss": "4.0.0-beta.9", - "@tailwindcss/vite": "4.0.0-beta.9", - "@tanstack/vue-table": "^8.20.5", - "@types/color": "^4.2.0", - "@unhead/vue": "^1.11.16", - "@vueuse/core": "^12.4.0", - "@vueuse/integrations": "^12.4.0", - "color": "^4.2.3", - "consola": "^3.3.3", + "@tailwindcss/postcss": "^4.0.6", + "@tailwindcss/vite": "^4.0.6", + "@tanstack/vue-table": "^8.21.2", + "@unhead/vue": "^1.11.19", + "@vueuse/core": "^12.7.0", + "@vueuse/integrations": "^12.7.0", + "colortranslator": "^4.1.0", + "consola": "^3.4.0", "defu": "^6.1.4", "embla-carousel-auto-height": "^8.5.2", "embla-carousel-auto-scroll": "^8.5.2", @@ -2424,23 +2435,23 @@ "embla-carousel-fade": "^8.5.2", "embla-carousel-vue": "^8.5.2", "embla-carousel-wheel-gestures": "^8.0.1", - "fuse.js": "^7.0.0", + "fuse.js": "^7.1.0", "get-port-please": "^3.1.2", "knitwork": "^1.2.0", "magic-string": "^0.30.17", "mlly": "^1.7.4", "ohash": "^1.1.4", - "pathe": "^2.0.1", - "reka-ui": "1.0.0-alpha.8", + "pathe": "^2.0.3", + "reka-ui": "1.0.0-alpha.10", "scule": "^1.3.0", "sirv": "^3.0.0", - "tailwind-variants": "^0.3.0", - "tailwindcss": "4.0.0-beta.9", + "tailwind-variants": "^0.3.1", + "tailwindcss": "^4.0.6", "tinyglobby": "^0.2.10", - "unplugin": "^2.1.2", - "unplugin-auto-import": "^19.0.0", - "unplugin-vue-components": "^28.0.0", - "vaul-vue": "^0.2.0" + "unplugin": "^2.2.0", + "unplugin-auto-import": "^19.1.0", + "unplugin-vue-components": "^28.1.0", + "vaul-vue": "^0.2.1" }, "bin": { "nuxt-ui": "cli/index.mjs" @@ -2449,6 +2460,172 @@ "typescript": "^5.6.3" } }, + "node_modules/@nuxt/ui/node_modules/@nuxt/devtools-kit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-2.1.0.tgz", + "integrity": "sha512-1fhwU7dDq/vIpjpNRwjEmTllRT1O0nzyBEhY187bQ8xBpoCC93t3zG3iTKcl8XkpT1aK9SqcgmXOnj5fNIAaYA==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.15.4", + "@nuxt/schema": "^3.15.4", + "execa": "^9.5.2" + }, + "peerDependencies": { + "vite": ">=6.0" + } + }, + "node_modules/@nuxt/ui/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/execa": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@nuxt/ui/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/human-signals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@nuxt/ui/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nuxt/ui/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/ui/node_modules/unplugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz", + "integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/@nuxt/vite-builder": { "version": "3.15.4", "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.15.4.tgz", @@ -3382,6 +3559,12 @@ "win32" ] }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -3423,42 +3606,42 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.0-beta.9.tgz", - "integrity": "sha512-KuKNhNVU5hd2L5BkXE/twBKkMnHG4wQiHes6axhDbdcRew0/YZtvlWvMIy7QmtBWnR1lM8scPhp0RXmxK/hZdw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.6.tgz", + "integrity": "sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==", "license": "MIT", "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", - "tailwindcss": "4.0.0-beta.9" + "tailwindcss": "4.0.6" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-beta.9.tgz", - "integrity": "sha512-1bpui84CDnrjB6TI3AGR9jYUA28+VIfkrM4BH3+VXA9B80+cARtd3ON06ouA5/r/2xs4qe+T85Z1c0k5X6vLeA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.6.tgz", + "integrity": "sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==", "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.0.0-beta.9", - "@tailwindcss/oxide-darwin-arm64": "4.0.0-beta.9", - "@tailwindcss/oxide-darwin-x64": "4.0.0-beta.9", - "@tailwindcss/oxide-freebsd-x64": "4.0.0-beta.9", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-beta.9", - "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-beta.9", - "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-beta.9", - "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-beta.9", - "@tailwindcss/oxide-linux-x64-musl": "4.0.0-beta.9", - "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0-beta.9", - "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-beta.9" + "@tailwindcss/oxide-android-arm64": "4.0.6", + "@tailwindcss/oxide-darwin-arm64": "4.0.6", + "@tailwindcss/oxide-darwin-x64": "4.0.6", + "@tailwindcss/oxide-freebsd-x64": "4.0.6", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.6", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.6", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.6", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.6", + "@tailwindcss/oxide-linux-x64-musl": "4.0.6", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.6", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.6" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-beta.9.tgz", - "integrity": "sha512-MiDpTfYvRozM+40mV2wh7GCxyEj7zIOtX3bRNaJgu0adxzZaKkylks46kBY8X91NV3ch6CQSf9Zlr0vi4U5qdw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.6.tgz", + "integrity": "sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==", "cpu": [ "arm64" ], @@ -3472,9 +3655,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-beta.9.tgz", - "integrity": "sha512-SjdLul42NElqSHO5uINXylMNDx4KjtN3iB2o5nv0dFJV119DB0rxSCswgSEfigqyMXLyOAw3dwdoJIUFiw5Sdg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.6.tgz", + "integrity": "sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==", "cpu": [ "arm64" ], @@ -3488,9 +3671,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-beta.9.tgz", - "integrity": "sha512-pmAs3H+pYUxAYbz2y7Q2tIfcNVlnPiikZN0SejF7JaDROg4PhQsWWpvlzHZZvD6CuyFCRXayudG8PwpJSk29dg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.6.tgz", + "integrity": "sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==", "cpu": [ "x64" ], @@ -3504,9 +3687,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-beta.9.tgz", - "integrity": "sha512-l39LttvdeeueMxuVNn1Z/cNK1YMWNzoIUgTsHCgF2vhY9tl4R+QcSwlviAkvw4AkiAC4El84pGBBVGswyWa8Rw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.6.tgz", + "integrity": "sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==", "cpu": [ "x64" ], @@ -3520,9 +3703,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-beta.9.tgz", - "integrity": "sha512-sISzLGpVXNqOYJTo7KcdtUWQulZnW7cqFanBNbe8tCkS1KvlIuckC3MWAihLxpLrmobKh/Wv+wB1aE08VEfCww==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.6.tgz", + "integrity": "sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==", "cpu": [ "arm" ], @@ -3536,9 +3719,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-beta.9.tgz", - "integrity": "sha512-8nmeXyBchcqzQtyqjnmMxlLyxBPd+bwlnr5tDr3w6yol0z7Yrfz3T6L4QoZ4TbfhE26t6qWsUa+WQlzMJKsazg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.6.tgz", + "integrity": "sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==", "cpu": [ "arm64" ], @@ -3552,9 +3735,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-beta.9.tgz", - "integrity": "sha512-x+Vr4SnZayMj5PEFHL7MczrvjK7fYuv2LvakPfXoDYnAOmjhrjX5go3I0Q65uUPWiZxGcS/y0JgAtQqgHSKU8A==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.6.tgz", + "integrity": "sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==", "cpu": [ "arm64" ], @@ -3568,9 +3751,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-beta.9.tgz", - "integrity": "sha512-4HpvDn3k5P623exDRbo9rjEXcIuHBj3ZV9YcnWJNE9QZ2vzKXGXxCxPuShTAg25JmH8z+b2whmFsnbxDqtgKhA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.6.tgz", + "integrity": "sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==", "cpu": [ "x64" ], @@ -3584,9 +3767,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-beta.9.tgz", - "integrity": "sha512-RgJrSk7uAt5QC7ez0p0uNcd/Z0yoXuBL9VvMnZVdEMDA7dcf1/zMCcFt3p2nGsGY7q2qp0hULdBEhsRP2Gq0cw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.6.tgz", + "integrity": "sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==", "cpu": [ "x64" ], @@ -3600,9 +3783,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0-beta.9.tgz", - "integrity": "sha512-FCpprAxJqDT27C2OaJTAR06+BsmHS2gW7Wu0lC9E6DwiizYP0YjSVFeYvnkluE5O2J4uVR3X2GAaqxbtG4z9Ug==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.6.tgz", + "integrity": "sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==", "cpu": [ "arm64" ], @@ -3616,9 +3799,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-beta.9.tgz", - "integrity": "sha512-KOf2YKFwrvFVX+RNJsYVC6tsWBxDMTX7/u4SpUepqkwVgq2yCObx/Sqt820lXuKgGJ9dKsTYF2wvMUGom7B71A==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.6.tgz", + "integrity": "sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==", "cpu": [ "x64" ], @@ -3632,38 +3815,38 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.0-beta.9.tgz", - "integrity": "sha512-xXqMnXtg8K2FgrIlqSf3PPHgyAuSiGe7BJ6+6wma96s7VXArsN5UtTwDuksAedJtCymk1liTvLa2eRNrumlavA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.6.tgz", + "integrity": "sha512-noTaGPHjGCXTCc487TWnfAEN0VMjqDAecssWDOsfxV2hFrcZR0AHthX7IdY/0xHTg/EtpmIPdssddlZ5/B7JnQ==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.0.0-beta.9", - "@tailwindcss/oxide": "4.0.0-beta.9", - "lightningcss": "^1.29.0", + "@tailwindcss/node": "^4.0.6", + "@tailwindcss/oxide": "^4.0.6", + "lightningcss": "^1.29.1", "postcss": "^8.4.41", - "tailwindcss": "4.0.0-beta.9" + "tailwindcss": "4.0.6" } }, "node_modules/@tailwindcss/vite": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.0-beta.9.tgz", - "integrity": "sha512-Hf28QkwSLM6bbOkcTQk1iEEOB37v+9vfqdpHUaLSluZpEGCVAFc0i+p2Gvp6MlK840tyixQu5L39VjL2lAZFFQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.6.tgz", + "integrity": "sha512-O25vZ/URWbZ2JHdk2o8wH7jOKqEGCsYmX3GwGmYS5DjE4X3mpf93a72Rn7VRnefldNauBzr5z2hfZptmBNtTUQ==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.0.0-beta.9", - "@tailwindcss/oxide": "4.0.0-beta.9", - "lightningcss": "^1.29.0", - "tailwindcss": "4.0.0-beta.9" + "@tailwindcss/node": "^4.0.6", + "@tailwindcss/oxide": "^4.0.6", + "lightningcss": "^1.29.1", + "tailwindcss": "4.0.6" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "node_modules/@tanstack/table-core": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", - "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", + "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", "license": "MIT", "engines": { "node": ">=12" @@ -3674,9 +3857,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz", - "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.0.tgz", + "integrity": "sha512-NBKJP3OIdmZY3COJdWkSonr50FMVIi+aj5ZJ7hI/DTpEKg2RMfo/KvP8A3B/zOSpMgIe52B5E2yn7rryULzA6g==", "license": "MIT", "funding": { "type": "github", @@ -3684,12 +3867,12 @@ } }, "node_modules/@tanstack/vue-table": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.20.5.tgz", - "integrity": "sha512-2xixT3BEgSDw+jOSqPt6ylO/eutDI107t2WdFMVYIZZ45UmTHLySqNriNs0+dMaKR56K5z3t+97P6VuVnI2L+Q==", + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.2.tgz", + "integrity": "sha512-KBgOWxha/x4m1EdhVWxOpqHb661UjqAxzPcmXR3QiA7aShZ547x19Gw0UJX9we+m+tVcPuLRZ61JsYW47QZFfQ==", "license": "MIT", "dependencies": { - "@tanstack/table-core": "8.20.5" + "@tanstack/table-core": "8.21.2" }, "engines": { "node": ">=12" @@ -3703,12 +3886,12 @@ } }, "node_modules/@tanstack/vue-virtual": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.11.2.tgz", - "integrity": "sha512-y0b1p1FTlzxcSt/ZdGWY1AZ52ddwSU69pvFRYAELUSdLLxV8QOPe9dyT/KATO43UCb3DAwiyzi96h2IoYstBOQ==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.0.tgz", + "integrity": "sha512-EPgcTc41KGJAK2N2Ux2PeUnG3cPpdkldTib05nwq+0zdS2Ihpbq8BsWXz/eXPyNc5noDBh1GBgAe36yMYiW6WA==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.11.2" + "@tanstack/virtual-core": "3.13.0" }, "funding": { "type": "github", @@ -3727,30 +3910,6 @@ "node": ">=10.13.0" } }, - "node_modules/@types/color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/color/-/color-4.2.0.tgz", - "integrity": "sha512-6+xrIRImMtGAL2X3qYkd02Mgs+gFGs+WsK0b7VVMaO4mYRISwyTjcqNrO0mNSmYEoq++rSLDB2F5HDNmqfOe+A==", - "license": "MIT", - "dependencies": { - "@types/color-convert": "*" - } - }, - "node_modules/@types/color-convert": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.4.tgz", - "integrity": "sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==", - "license": "MIT", - "dependencies": { - "@types/color-name": "^1.1.0" - } - }, - "node_modules/@types/color-name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz", - "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==", - "license": "MIT" - }, "node_modules/@types/doctrine": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", @@ -3827,20 +3986,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", - "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", + "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/type-utils": "8.22.0", - "@typescript-eslint/utils": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.23.0", + "@typescript-eslint/type-utils": "8.23.0", + "@typescript-eslint/utils": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3865,15 +4024,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", - "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz", + "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.23.0", + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/typescript-estree": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4" }, "engines": { @@ -3889,13 +4048,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", - "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz", + "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0" + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3906,15 +4065,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", - "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz", + "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/typescript-estree": "8.23.0", + "@typescript-eslint/utils": "8.23.0", "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3929,9 +4088,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", - "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz", + "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3942,19 +4101,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", - "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz", + "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3968,15 +4127,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", - "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz", + "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0" + "@typescript-eslint/scope-manager": "8.23.0", + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/typescript-estree": "8.23.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3991,12 +4150,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", - "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz", + "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/types": "8.23.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4008,13 +4167,39 @@ } }, "node_modules/@unhead/dom": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.18.tgz", - "integrity": "sha512-zQuJUw/et9zYEV0SZWTDX23IgurwMaXycAuxt4L6OgNL0T4TWP3a0J/Vm3Q02hmdNo/cPKeVBrwBdnFUXjGU4w==", + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.19.tgz", + "integrity": "sha512-udkgITdIblEWH3hsoFQMKW+6QXNO2qFZlZ2FI37bVAplQSnK/PytTPt/5oA1GWkoVwT0DsQNGHbU6kOg/3SlNg==", "license": "MIT", "dependencies": { - "@unhead/schema": "1.11.18", - "@unhead/shared": "1.11.18" + "@unhead/schema": "1.11.19", + "@unhead/shared": "1.11.19" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/dom/node_modules/@unhead/schema": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz", + "integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==", + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/dom/node_modules/@unhead/shared": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz", + "integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==", + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.19", + "packrup": "^0.1.2" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" @@ -4060,15 +4245,15 @@ } }, "node_modules/@unhead/vue": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.18.tgz", - "integrity": "sha512-Jfi7t/XNBnlcauP9UTH3VHBcS69G70ikFd2e5zdgULLDRWpOlLs1sSTH1V2juNptc93DOk9RQfC5jLWbLcivFw==", + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.19.tgz", + "integrity": "sha512-/XATTP8wVLs3+2Pkj2crvr/Z55nybVQyOwISh+sAlr/48/9n3jGNiCZHKpHgL4MpOnGT4krwzWzbfhBO/G2BSQ==", "license": "MIT", "dependencies": { - "@unhead/schema": "1.11.18", - "@unhead/shared": "1.11.18", + "@unhead/schema": "1.11.19", + "@unhead/shared": "1.11.19", "hookable": "^5.5.3", - "unhead": "1.11.18" + "unhead": "1.11.19" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" @@ -4077,6 +4262,32 @@ "vue": ">=2.7 || >=3" } }, + "node_modules/@unhead/vue/node_modules/@unhead/schema": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz", + "integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==", + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/vue/node_modules/@unhead/shared": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz", + "integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==", + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.19", + "packrup": "^0.1.2" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, "node_modules/@vercel/nft": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.10.tgz", @@ -4546,14 +4757,14 @@ } }, "node_modules/@vueuse/core": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.5.0.tgz", - "integrity": "sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.7.0.tgz", + "integrity": "sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "12.5.0", - "@vueuse/shared": "12.5.0", + "@vueuse/metadata": "12.7.0", + "@vueuse/shared": "12.7.0", "vue": "^3.5.13" }, "funding": { @@ -4561,13 +4772,13 @@ } }, "node_modules/@vueuse/integrations": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.5.0.tgz", - "integrity": "sha512-HYLt8M6mjUfcoUOzyBcX2RjpfapIwHPBmQJtTmXOQW845Y/Osu9VuTJ5kPvnmWJ6IUa05WpblfOwZ+P0G4iZsQ==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.7.0.tgz", + "integrity": "sha512-IEq7K4bCl7mn3uKJaWtNXnd1CAPaHLUMuyj5K1/k/pVcItt0VONZW8xiGxdIovJcQjkzOHjImhX5t6gija+0/g==", "license": "MIT", "dependencies": { - "@vueuse/core": "12.5.0", - "@vueuse/shared": "12.5.0", + "@vueuse/core": "12.7.0", + "@vueuse/shared": "12.7.0", "vue": "^3.5.13" }, "funding": { @@ -4627,18 +4838,18 @@ } }, "node_modules/@vueuse/metadata": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.5.0.tgz", - "integrity": "sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.7.0.tgz", + "integrity": "sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.5.0.tgz", - "integrity": "sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.7.0.tgz", + "integrity": "sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==", "license": "MIT", "dependencies": { "vue": "^3.5.13" @@ -5583,19 +5794,6 @@ "node": ">=0.10.0" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5614,16 +5812,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -5636,6 +5824,12 @@ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "license": "MIT" }, + "node_modules/colortranslator": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/colortranslator/-/colortranslator-4.1.0.tgz", + "integrity": "sha512-bwa5awaMnQ6dpm9D3nbsFwUr6x6FrTKmxPdolNtSYfxCNR7ZM93GG1OF5Y3Sy1LvYdalb3riKC9uTn0X5NB36g==", + "license": "Apache-2.0" + }, "node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -6531,9 +6725,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -6543,31 +6737,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "node_modules/escalade": { @@ -6598,17 +6792,17 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.20.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", + "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", + "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -6672,9 +6866,9 @@ } }, "node_modules/eslint-flat-config-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-2.0.0.tgz", - "integrity": "sha512-AbpYwI9FBmjF6BQ8UcaDCrM750DWEB6UJzEjQEg+iWFP6UX9rGsUGJlMf7sWbW3dOA0klUEwmWGZa5FoynXU/w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-2.0.1.tgz", + "integrity": "sha512-brf0eAgQ6JlKj3bKfOTuuI7VcCZvi8ZCD1MMTVoEvS/d38j8cByZViLFALH/36+eqB17ukmfmKq3bWzGvizejA==", "license": "MIT", "dependencies": { "pathe": "^2.0.2" @@ -6927,6 +7121,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", + "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7358,6 +7564,21 @@ } } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7593,9 +7814,9 @@ } }, "node_modules/fuse.js": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", - "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", + "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", "license": "Apache-2.0", "engines": { "node": ">=10" @@ -7857,9 +8078,9 @@ } }, "node_modules/happy-dom": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.8.1.tgz", - "integrity": "sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.1.0.tgz", + "integrity": "sha512-9tUhXyePCjzUMycaHS/IzrIpF69xiq/laAT7golk4MtZ6t8ft5+Rv7U3lfrs2b4NMH0JTL3EhZzjfahrPmOnaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8127,12 +8348,6 @@ "url": "https://github.com/sponsors/brc-dd" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -8281,6 +8496,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -8311,6 +8538,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -8366,9 +8605,9 @@ "license": "ISC" }, "node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.3.tgz", + "integrity": "sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -9997,6 +10236,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-path": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", @@ -10090,9 +10341,9 @@ } }, "node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, "node_modules/pathval": { @@ -10659,6 +10910,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -10736,9 +11002,9 @@ "license": "MIT" }, "node_modules/radix-vue": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.12.tgz", - "integrity": "sha512-zkr66Jqxbej4+oR6O/pZRzyM/VZi66ndbyIBZQjJKAXa1lIoYReZJse6W1EEDZKXknD7rXhpS+jM9Sr23lIqfg==", + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.15.tgz", + "integrity": "sha512-+wz/PBlUTkrqFPS9ZUI1eZ4QHrU2EVbQnPvtnxWY7Q7jZtNE/LAAEf9IDoBoacX1hKfJXBB+SKfZSrv57uofYA==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.7", @@ -11134,18 +11400,18 @@ } }, "node_modules/reka-ui": { - "version": "1.0.0-alpha.8", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-1.0.0-alpha.8.tgz", - "integrity": "sha512-FmAUxWFLWtvbheBLvjgotR/RsE1KSjciMJOLmo7wL0Sbe+sW7M35O8K6f141a0Vc1cE0mH57UHcWBuVpeJNQNA==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.6.11", - "@floating-ui/vue": "^1.1.5", - "@internationalized/date": "^3.5.6", - "@internationalized/number": "^3.5.4", - "@tanstack/vue-virtual": "^3.10.8", - "@vueuse/core": "^12.0.0", - "@vueuse/shared": "^12.0.0", + "version": "1.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-1.0.0-alpha.10.tgz", + "integrity": "sha512-fQ+7LLKlZ9dTFOMsSmefRBDLEnFdOcstWW3ZIaV7000d8YYsteYZOY86WWRi7k64FuWn8pMIE8Ya3Aq0pkc7lw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.13", + "@floating-ui/vue": "^1.1.6", + "@internationalized/date": "^3.5.0", + "@internationalized/number": "^3.5.0", + "@tanstack/vue-virtual": "^3.12.0", + "@vueuse/core": "^12.5.0", + "@vueuse/shared": "^12.5.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^1.1.4" @@ -11615,15 +11881,6 @@ "url": "https://github.com/steveukx/git-js?sponsor=1" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/sirv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", @@ -12128,9 +12385,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.0.0-beta.9", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.9.tgz", - "integrity": "sha512-96KpsfQi+/sFIOfyFnGzyy5pobuzf1iMBD9NVtelerPM/lPI2XUS4Kikw9yuKRniXXw77ov1sl7gCSKLsn6CJA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.6.tgz", + "integrity": "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==", "license": "MIT" }, "node_modules/tapable": { @@ -12323,9 +12580,9 @@ "license": "MIT" }, "node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", "license": "MIT", "engines": { "node": ">=18.12" @@ -12451,20 +12708,46 @@ "license": "MIT" }, "node_modules/unhead": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.18.tgz", - "integrity": "sha512-TWgGUoZMpYe2yJwY6jZ0/9kpQT18ygr2h5lI6cUXdfD9UzDc0ytM9jGaleSYkj9guJWXkk7izYBnzJvxl8mRvQ==", + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.19.tgz", + "integrity": "sha512-O5AYb3+xUOzBlwDmPfC/DgGp9rDMoGkB4gFkhoaz8IonQqP8W8qqetxYf5ZyEdntvXnFsMWS8lZF//5176xo6Q==", "license": "MIT", "dependencies": { - "@unhead/dom": "1.11.18", - "@unhead/schema": "1.11.18", - "@unhead/shared": "1.11.18", + "@unhead/dom": "1.11.19", + "@unhead/schema": "1.11.19", + "@unhead/shared": "1.11.19", "hookable": "^5.5.3" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" } }, + "node_modules/unhead/node_modules/@unhead/schema": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz", + "integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==", + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/unhead/node_modules/@unhead/shared": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz", + "integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==", + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.19", + "packrup": "^0.1.2" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, "node_modules/unicode-properties": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", @@ -12611,18 +12894,17 @@ } }, "node_modules/unplugin-auto-import": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-19.0.0.tgz", - "integrity": "sha512-TREXtXqCM6YLy3rE2tjvKZEaCiPlP2e5bmnRKaS8AM2MlNgjV7UP4RPieWIfs4Isv0GoeHmov956PIIvJYdqpQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-19.1.0.tgz", + "integrity": "sha512-B+TGBEBHqY9aR+7YfShfLujETOHstzpV+yaqgy5PkfV0QG7Py+TYMX7vJ9W4SrysHR+UzR+gzcx/nuZjmPeclA==", "license": "MIT", "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.4", - "local-pkg": "^0.5.1", + "local-pkg": "^1.0.0", "magic-string": "^0.30.17", "picomatch": "^4.0.2", - "unimport": "^3.14.5", - "unplugin": "^2.1.2" + "unimport": "^4.1.1", + "unplugin": "^2.2.0", + "unplugin-utils": "^0.2.4" }, "engines": { "node": ">=14" @@ -12643,22 +12925,118 @@ } } }, + "node_modules/unplugin-auto-import/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-auto-import/node_modules/local-pkg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz", + "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.3.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unplugin-auto-import/node_modules/tinyglobby": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.11.tgz", + "integrity": "sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/unplugin-auto-import/node_modules/unimport": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.1.2.tgz", + "integrity": "sha512-oVUL7PSlyVV3QRhsdcyYEMaDX8HJyS/CnUonEJTYA3//bWO+o/4gG8F7auGWWWkrrxBQBYOO8DKe+C53ktpRXw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.1", + "scule": "^1.3.0", + "strip-literal": "^3.0.0", + "tinyglobby": "^0.2.11", + "unplugin": "^2.2.0", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import/node_modules/unplugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz", + "integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.4.tgz", + "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/unplugin-vue-components": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-28.0.0.tgz", - "integrity": "sha512-vYe0wSyqTVhyNFIad1iiGyQGhG++tDOMgohqenMDOAooMJP9vvzCdXTqCVx20A0rCQXFNjgoRbSeDAioLPH36Q==", + "version": "28.2.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-28.2.0.tgz", + "integrity": "sha512-FMncMZRnOcVs81BligpZL4tPFHE+VX1P1SYgzzl4D6ZgLiJHoZWNx743z+Vfyz701GsZ8WX/ENvYrIZ4mvJPeA==", "license": "MIT", "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.4", "chokidar": "^3.6.0", "debug": "^4.4.0", - "fast-glob": "^3.3.3", - "local-pkg": "^0.5.1", + "local-pkg": "^1.0.0", "magic-string": "^0.30.17", - "minimatch": "^9.0.5", - "mlly": "^1.7.3", - "unplugin": "^2.1.2" + "mlly": "^1.7.4", + "tinyglobby": "^0.2.10", + "unplugin": "^2.2.0", + "unplugin-utils": "^0.2.4" }, "engines": { "node": ">=14" @@ -12716,6 +13094,22 @@ "node": ">= 6" } }, + "node_modules/unplugin-vue-components/node_modules/local-pkg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz", + "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.3.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/unplugin-vue-components/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -12740,6 +13134,19 @@ "node": ">=8.10.0" } }, + "node_modules/unplugin-vue-components/node_modules/unplugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz", + "integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/unplugin-vue-router": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.11.2.tgz", @@ -13140,9 +13547,9 @@ "license": "MIT" }, "node_modules/valibot": { - "version": "1.0.0-beta.15", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.15.tgz", - "integrity": "sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw==", + "version": "1.0.0-rc.1", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-rc.1.tgz", + "integrity": "sha512-bTHNpeeQ403xS7qGHF/tw3EC/zkZOU5VdkfIsmRDu1Sp+BJNTNCm6m5HlwOgyW/03lofP+uQiq3R+Poo9wiCEg==", "license": "MIT", "peerDependencies": { "typescript": ">=5" @@ -13174,9 +13581,9 @@ } }, "node_modules/vaul-vue": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/vaul-vue/-/vaul-vue-0.2.0.tgz", - "integrity": "sha512-YV0zqxc8NiVzr1z/Awwbaty0UDDchxj5BfhFbLiYu+Uz0rCfSaDK2zwmuXZvejBJKLGbWw9I5GLHJRse14lQew==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vaul-vue/-/vaul-vue-0.2.1.tgz", + "integrity": "sha512-iF91R1JQZzxb9mb9uGNHYv8rVFxR5bL5Fj51iqvyXjzMPAzNMciCrXb9OUBu2NdlhcF6rVtEADXnQoTY+pKIeA==", "dependencies": { "@vueuse/core": "^10.8.0", "radix-vue": "^1.4.9", @@ -14260,6 +14667,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zhead": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz", diff --git a/package.json b/package.json index ed2f66b..4748729 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "build": "node_modules/.bin/nuxt build", - "dev": "node_modules/.bin/nuxt dev", + "dev": "node_modules/.bin/nuxt dev --dotenv .env", "generate": "node_modules/.bin/nuxt generate", "lint": "node_modules/.bin/eslint .", "postinstall": "node_modules/.bin/nuxt prepare", @@ -14,25 +14,28 @@ "test": "node_modules/.bin/vitest run --passWithNoTests" }, "dependencies": { - "@iconify-json/material-symbols": "^1.2.14", - "@nuxt/eslint": "^1.0.0", - "@nuxt/ui": "^3.0.0-alpha.11", + "@nuxt/eslint": "^1.0.1", + "@nuxt/ui": "^3.0.0-alpha.13", "@nuxtjs/leaflet": "^1.2.6", - "eslint": "^9.19.0", + "eslint": "^9.20.1", "leaflet.markercluster": "^1.5.3", "nuxt": "^3.15.4", - "valibot": "^1.0.0-beta.15", + "valibot": "^1.0.0-rc.1", "vue": "latest", "vue-router": "latest" }, "devDependencies": { + "@iconify-json/lucide": "^1.2.26", + "@iconify-json/material-symbols": "^1.2.14", "@nuxt/test-utils": "^3.15.4", "@vue/test-utils": "^2.4.6", - "happy-dom": "^16.8.1", + "happy-dom": "^17.1.0", "typescript": "^5.7.3", "vitest": "^3.0.5" }, "overrides": { - "glob": "^11.0.1" + "glob": "^11.0.1", + "happy-dom": "^17.1.0", + "esbuild": "^0.25.0" } } diff --git a/package.json.md b/package.json.md new file mode 100644 index 0000000..9a05fcc --- /dev/null +++ b/package.json.md @@ -0,0 +1,12 @@ +Dépendances outrepassées : +- `glob` with version "^11.0.1", waiting update from these packages: + - @vue/test-utils@2.4.6 + - nuxt@3.15.4 +- `happy-dom` with version "^17.1.0", waiting update from these packages: + - @nuxt/test-utils@3.15.4 + - vitest@3.0.5 +- `esbuild` with version "^0.25.0", waiting update from these packages: + - @nuxt@3.15.4 + - @nuxt/test-utils@3.15.4 + - @nuxt/ui@3.0.0-alpha.13 + - @nuxt/eslint@1.0.1 diff --git a/pages/exercices/[id].vue b/pages/exercices/[id].vue deleted file mode 100644 index 8e436cc..0000000 --- a/pages/exercices/[id].vue +++ /dev/null @@ -1,3 +0,0 @@ -<template> - <div/> -</template> \ No newline at end of file diff --git a/pages/exercise/[id].vue b/pages/exercise/[id].vue new file mode 100644 index 0000000..8dea90f --- /dev/null +++ b/pages/exercise/[id].vue @@ -0,0 +1,149 @@ +<script setup lang="ts"> +import * as v from 'valibot' +import {ModalConfirmation} from "#components"; +import {getExerciseCategoryIcon, translateExerciseCategoryToFrench} from "~/types/enum/ExerciseCategory"; +import type {ResponseBody} from "~/types/ResponseBody"; +import type {IExercise} from "~/repository/model/IExercise"; + +const {$api} = useNuxtApp(); +const modal = useModal(); +const route = useRoute(); +const router = useRouter(); +const toast = useToast(); + +const isCreation = route.params.id === 'new'; +const mode = route.query.mode; +const form = useTemplateRef('form'); +const exercise: Exercise = reactive(await $api.exercises.getExercise(parseInt(route.params.id as string))); +const categories = Object.values(ExerciseCategory).map((category) => { + return { + label: translateExerciseCategoryToFrench(category), + value: category, + icon: getExerciseCategoryIcon(category) + } +}); +const subCategories = ref<string[]>(await $api.exercises.fetchSubCategories()); + +const schema = v.object({ + id: v.pipe(v.number()), + name: v.pipe(v.string(), v.nonEmpty('Champs requis')), + description: v.pipe(v.string(), v.nonEmpty('Champs requis')), + category: v.pipe(v.string(), v.nonEmpty('Champs requis')), + subCategory: v.pipe(v.string(), v.nonEmpty('Champs requis')), + diagram: v.union([v.pipe(v.string(), v.url('URL incorrect')), v.literal('')]), + video: v.union([v.pipe(v.string(), v.url('URL incorrect')), v.literal('')]) +}); + +function onCreate(item: string) { + subCategories.value.push(item) + + // TODO : Need api update + // Gestion d'une nouvelle sous catégorie ? + // affichage d'une pop-up ? + exercise.subCategory = item +} + +async function onSubmit() { + const data = await $api.exercises.saveExercise(exercise) as ResponseBody<string | IExercise>; + + if (data.statusCode === 200 || data.statusCode === 201) { + toast.add({title: 'Success', description: 'Votre exercice a bien été enregistré.', color: 'success'}) + await router.push('/exercises'); + } else if (data.statusCode === 401) { + toast.add({title: 'Error', description: 'Vous n\'êtes pas autorisé à effectuer cette action.' , color: 'error'}) + } else if (data.statusCode === 403) { + toast.add({title: 'Error', description: 'Vous devez vous identifier avant d\'effectuer cette action.', color: 'error'}) + } else { + console.error('Error saving exercise', data); + toast.add({title: 'Error', description: 'Error updating exercise', color: 'error'}) + } +} + +function onCancel() { + if (form.value?.dirty) { + modal.open(ModalConfirmation, { + title: 'Quitter sans enregistrer ?', + description: 'Toutes vos modifications seront perdues.', + confirmIcon: 'material-symbols:googler-travel', + onSuccess() { + modal.close() + router.push('/exercises') + } + }) + } else { + router.push('/exercises') + } +} +</script> + +<template> + <div id="form-exercise" class="flex flex-col items-center justify-center"> + <UButton icon="material-symbols:arrow-back-rounded" class="self-start" @click="onCancel">Retour</UButton> + <UForm + ref="form" + :disabled="mode !== 'edit'" + :schema="v.safeParser(schema)" + :state="exercise" + :validate-on="['input', 'change']" + class="space-y-4 grid grid-cols-2 gap-2 h-full" + @submit="onSubmit" + > + <UFormField v-if="!isCreation" :inert="!isCreation" label="Identifiant" name="id"> + <UInput :value="exercise.id" type="number" disabled/> + </UFormField> + + <UFormField label="Nom de l'exercice" name="name" required :class=" {'col-span-2' : isCreation }"> + <UInput v-model="exercise.name" placeholder="Nom de l'exercice" class="w-full"/> + </UFormField> + + <UFormField label="Description" name="description" required> + <UTextarea v-model="exercise.description" placeholder="Description" class="w-full"/> + </UFormField> + + <UFormField label="Instructions de sécurité" name="safety"> + <UTextarea v-model="exercise.safetyInstructions" placeholder="Instructions de sécurité" class="w-full"/> + </UFormField> + + <UFormField label="Consignes" name="general" class="col-span-2"> + <UTextarea v-model="exercise.generalInstructions" placeholder="Consignes" class="w-full"/> + </UFormField> + + <UFormField label="Illustration" name="diagram"> + <UInput v-model="exercise.diagram" type="url" placeholder="Illustration" class="w-full"/> + </UFormField> + + <UFormField label="Vidéo" name="video"> + <UInput v-model="exercise.video" type="url" placeholder="Vidéo" class="w-full"/> + </UFormField> + + <UFormField label="Catégorie" name="category" required> + <USelect v-model="exercise.category" :items="categories" class="w-full"/> + </UFormField> + + <UFormField label="Sous-catégorie" name="subCategory" required> + <USelectMenu + v-model="exercise.subCategory" + :items="subCategories" + create-item + class="w-full" + placeholder="Sous-catégorie" + @create="onCreate"/> + </UFormField> + + <UButton type="button" class="place-self-center m-auto w-fit" color="secondary" icon="material-symbols:cancel-outline" size="xl" @click="onCancel"> + Annuler + </UButton> + <UButton type="submit" class="w-fit place-self-center" :disabled="mode !== 'edit'" icon="material-symbols:save-outline" size="xl"> + Enregistrer + </UButton> + </UForm> + </div> +</template> + +<style> +div#form-exercise { + padding: 1vh 1vw 6vh 1vw; + height: 80vh; + overflow: auto; +} +</style> \ No newline at end of file diff --git a/pages/exercises.vue b/pages/exercises.vue new file mode 100644 index 0000000..d994b31 --- /dev/null +++ b/pages/exercises.vue @@ -0,0 +1,211 @@ +<script setup lang="ts"> +import {h} from "vue"; +import type {DropdownMenuItem, TableColumn} from "@nuxt/ui"; +import {getPaginationRowModel, type SortDirection} from "@tanstack/vue-table"; +import {getExerciseCategoryIcon, translateExerciseCategoryToFrench} from "~/types/enum/ExerciseCategory"; + +const UButton = resolveComponent('UButton'); +const UBadge = resolveComponent('UBadge'); +const UDropdownMenu = resolveComponent('UDropdownMenu'); + +const {$api} = useNuxtApp(); +const router = useRouter(); +const table = useTemplateRef('table'); +const toast = useToast(); + +const data = ref(await $api.exercises.getExercises()); +const globalFilter = ref(''); +const sorting = ref([ + { + id: 'id', + desc: true + } +]); +const pagination = ref({ + pageIndex: 0, + pageSize: 10 +}); + +// Create the columns for the table +const columns: TableColumn<Exercise>[] = [ + { + accessorKey: 'id', + header: ({column}) => { + return h(UButton, { + color: 'neutral', + variant: 'ghost', + label: '#', + icon: getSortIcon(column.getIsSorted()), + class: '-mx-2.5', + onClick: () => column.toggleSorting(column.getIsSorted() === 'asc') + }) + }, + cell: ({row}) => `#${row.getValue('id')}` + }, + { + accessorKey: 'name', + header: ({column}) => { + return h(UButton, { + color: 'neutral', + variant: 'ghost', + label: 'Nom de l\'exercice', + icon: getSortIcon(column.getIsSorted()), + class: '-mx-2.5', + onClick: () => column.toggleSorting(column.getIsSorted() === 'asc') + }) + }, + cell: ({row}) => { + return h('div', {class: 'font-medium'}, row.getValue('name')) + } + }, + { + accessorKey: 'category', + header: 'Catégorie', + cell: ({row}) => { + return h( + UBadge, + {class: 'capitalize justify-center', variant: 'subtle', color:'neutral', icon: getExerciseCategoryIcon(row.getValue('category'))}, + () => translateExerciseCategoryToFrench(row.getValue('category')) + ) + } + }, + { + id: 'actions', + cell: ({row}) => { + return h( + 'div', + {class: 'text-right'}, + h( + UDropdownMenu, + { + content: { + align: 'end' + }, + items: getDropdownActions(row.original) + }, + () => + h(UButton, { + icon: 'i-lucide-ellipsis-vertical', + color: 'neutral', + variant: 'ghost', + class: 'ml-auto' + }) + ) + ) + } + } +]; + +// Get the icon for the sorting direction +function getSortIcon(isSorted: false | SortDirection) { + if (!isSorted) return 'i-lucide-arrow-up-down' + return isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow' +} + +// Calculate the number of displayed elements +function getDisplayedElementsCount(pageIndex: number, pageSize: number, totalElements: number): number { + const startIndex = pageIndex * pageSize; + const endIndex = Math.min(startIndex + pageSize, totalElements); + return endIndex - startIndex; +} + +// Get the dropdown actions for the given exercise such as copy user id, view, edit and delete. +function getDropdownActions(exercise: Exercise): DropdownMenuItem[][] { + return [ + [ + { + label: 'Copier l\'identifiant', + icon: 'material-symbols:content-copy-outline', + onSelect: () => { + navigator.clipboard.writeText(exercise.id.toString()) + toast.add({ + title: 'Identifiant de l\'exercice copié dans le presse-papier.', + color: 'success', + icon: 'material-symbols:check-circle-outline' + }) + } + } + ], + [ + { + label: 'Afficher', + icon: 'material-symbols:visibility', + onSelect: () => { + router.push(`/exercise/${exercise.id}`) + } + }, + { + label: 'Modifier', + icon: 'material-symbols:edit', + onSelect: () => { + router.push(`/exercise/${exercise.id}?mode=edit`) + } + }, + { + label: 'Supprimer', + icon: 'material-symbols:delete-forever-outline', + color: 'error', + onSelect: async () => { + const response = await $api.exercises.deleteExercise(exercise.id) + if (response.statusCode === 200) { + data.value = await $api.exercises.getExercises() + toast.add({title: 'Success', description: 'Votre exercice a bien été enregistré.', color: 'success'}) + await router.push('/exercises'); + } else if (response.statusCode === 401) { + toast.add({title: 'Error', description: 'Vous n\'êtes pas autorisé à effectuer cette action.' , color: 'error'}) + } else if (response.statusCode === 403) { + toast.add({title: 'Error', description: 'Vous devez vous identifier avant d\'effectuer cette action.', color: 'error'}) + } else { + console.error('Error saving exercise', response); + toast.add({title: 'Error', description: 'Une erreur est survenue.', color: 'error'}) + } + } + } + ] + ] +} + +</script> + +<template> + <div id="exercises"> + <div class="flex justify-between"> + <UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..."/> + <NuxtLink to="/exercise/new?mode=edit"> + <UButton class="ml-2" icon="material-symbols:add-circle-outline-rounded">Ajouter un exercice</UButton> + </NuxtLink> + </div> + <UTable + ref="table" + v-model:sorting="sorting" + v-model:global-filter="globalFilter" + v-model:pagination="pagination" + :data="data" + :columns="columns" + :loading="data.length < 1" + :pagination-options="{ getPaginationRowModel: getPaginationRowModel() }" + class="flex-1 m-1 max-h-[70vh]" sticky + /> + + <div class="flex flex-col items-center border-t border-(--ui-border) pt-4"> + <UPagination + :default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1" + :items-per-page="table?.tableApi?.getState().pagination.pageSize" + :total="table?.tableApi?.getFilteredRowModel().rows.length" + @update:page="(p) => table?.tableApi?.setPageIndex(p - 1)" + /> + <p> + {{getDisplayedElementsCount(pagination.pageIndex, pagination.pageSize, table?.tableApi?.getFilteredRowModel().rows.length ?? 0) }} + / + {{ table?.tableApi?.getFilteredRowModel().rows.length }} + </p> + </div> + </div> +</template> + +<style> +div#exercises { + padding: 1vh 1vw 6vh 1vw; + height: 80vh; +} +</style> diff --git a/pages/index.vue b/pages/index.vue index ba99005..c9a6593 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -16,7 +16,7 @@ definePageMeta({ des exercices et parcours 3SPA </h2> <div class="flex flex-col items-center p-5 gap-2"> - <NuxtLink to="/exercices">Exercices</NuxtLink> + <NuxtLink to="/exercises">Exercices</NuxtLink> <NuxtLink to="/parcours">Parcours</NuxtLink> <NuxtLink to="/events">Évènements</NuxtLink> <NuxtLink to="/login">Se connecter</NuxtLink> diff --git a/pages/login.vue b/pages/login.vue index f53b092..848e148 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -1,28 +1,36 @@ <script setup lang="ts"> -import * as v from 'valibot' -import type {FormSubmitEvent} from '#ui/types' +import * as v from 'valibot'; +import type {FormSubmitEvent} from '#ui/types'; +import type {LoginBody} from "~/repository/model/LoginBody"; + +const {$api} = useNuxtApp(); definePageMeta({ layout: 'clear' -}) +}); const schema = v.object({ - email: v.pipe(v.string(), v.email('Invalid email')), - password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters')) -}) + email: v.pipe(v.string(), v.nonEmpty('Invalid username')), + password: v.pipe(v.string(), v.minLength(3, 'Must be at least 8 characters')) +}); type Schema = v.InferOutput<typeof schema> const state = reactive({ - email: 'a.a@a.aa', - password: 'azertyui' -}) + email: 'pam', + password: 'psw' +}); -const toast = useToast() -const router = useRouter() +const router = useRouter(); +const toast = useToast(); async function onSubmit(event: FormSubmitEvent<Schema>) { - if (event.data.email === 'a.a@a.aa') { + const body: LoginBody = { + username: event.data.email, + password: event.data.password + } + const data = await $api.users.postLogin(body); + if (data.message === 'Login successful') { toast.add({ title: 'Success', description: 'Connexion réussie.', @@ -31,10 +39,12 @@ async function onSubmit(event: FormSubmitEvent<Schema>) { icon: 'material-symbols:check' }) await router.push('/') - } else { - toast.add({title: 'Error', description: 'The form has not been submitted.', color: 'error'}) + } else if (data.error === 'Invalid username or password') { + toast.add({title: 'Error', description: data.error, color: 'error'}) state.password = '' return new Promise<void>(res => setTimeout(res, 1000)) + } else { + toast.add({title: 'Error', description: 'Une erreur est survenue.', color: 'error'}) } } </script> @@ -43,13 +53,13 @@ async function onSubmit(event: FormSubmitEvent<Schema>) { <div class="page-login h-full"> <h1>Login</h1><br> <UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4 page-login" @submit="onSubmit"> - <UFormField label="Email" name="email"> + <UFormField label="Email" name="email" required> <UInput v-model="state.email" placeholder="e-mail" icon="material-symbols:account-circle" variant="outline" color="primary"/> </UFormField> - <UFormField label="Password" name="password"> + <UFormField label="Password" name="password" required> <UInput v-model="state.password" type="password" placeholder="password" icon="material-symbols:key-vertical" color="primary"/> diff --git a/pages/parcours.vue b/pages/parcours.vue index 15e83c3..10aacf3 100644 --- a/pages/parcours.vue +++ b/pages/parcours.vue @@ -1,5 +1,4 @@ <script setup lang="ts"> -import Exercices from "~/pages/exercices.vue"; const items = [ { @@ -27,7 +26,7 @@ const stepper = useTemplateRef('stepper') <div id="parcours" class="w-full flex flex-col justify-between"> <UStepper ref="stepper" :items="items"> <template #exercises> - <Exercices/> + <ParcoursExercices/> </template> <template #parcours> @@ -35,7 +34,7 @@ const stepper = useTemplateRef('stepper') </template> <template #details> - Détails + <ParcoursDetails/> </template> </UStepper> diff --git a/plugins/api.ts b/plugins/api.ts new file mode 100644 index 0000000..c24ff4c --- /dev/null +++ b/plugins/api.ts @@ -0,0 +1,20 @@ +import ExerciseModule from "~/repository/module/exercise"; +import UserModule from '~/repository/module/user'; + +interface IApiInstance { + users: UserModule; + exercises: ExerciseModule; +} + +export default defineNuxtPlugin(() => { + const modules: IApiInstance = { + users: new UserModule(), + exercises: new ExerciseModule() + }; + + return { + provide: { + api: modules + } + }; +}); \ No newline at end of file diff --git a/repository/model/IExercise.ts b/repository/model/IExercise.ts new file mode 100644 index 0000000..913059b --- /dev/null +++ b/repository/model/IExercise.ts @@ -0,0 +1,15 @@ +export type IExercise = { + id: number; + name: string; + description: string; + safetyInstructions: string; + generalInstructions: string; + diagram: string; + video: string; + category: { + name: string + }, + subCategory: { + name: string + } +} \ No newline at end of file diff --git a/repository/model/IUser.ts b/repository/model/IUser.ts new file mode 100644 index 0000000..d50142a --- /dev/null +++ b/repository/model/IUser.ts @@ -0,0 +1,4 @@ +export type IUser = { + id: number; + login: string; +} \ No newline at end of file diff --git a/repository/model/LoginBody.ts b/repository/model/LoginBody.ts new file mode 100644 index 0000000..c605f01 --- /dev/null +++ b/repository/model/LoginBody.ts @@ -0,0 +1,4 @@ +export type LoginBody = { + username: string; + password: string; +} \ No newline at end of file diff --git a/repository/module/exercise.ts b/repository/module/exercise.ts new file mode 100644 index 0000000..79b489c --- /dev/null +++ b/repository/module/exercise.ts @@ -0,0 +1,105 @@ +import type {IExercise} from "~/repository/model/IExercise"; +import {type Exercise, ExerciseClass} from "~/types/Exercise"; +import type {ResponseBody} from "~/types/ResponseBody"; +import {ExerciseMapperFrom} from "~/utils/mapper/ExerciseMapper"; + +class ExerciseModule { + private readonly RESSOURCE: string = '/api/exercise' + private exercises: IExercise[] = []; + + /** + * Get an exercise by its id + * @param id the id of the exercise + * @return the exercise + */ + async getExercise(id: number): Promise<Exercise> { + if (!this.exercises.length || !this.exercises.find((e) => e.id === id)) { + await this.fetchExercises(); + } + const ex = this.exercises.find((e) => e.id === id) + if (!ex) { + return new ExerciseClass(this.getNewId()); + } + return ExerciseMapperFrom(ex); + } + + /** + * Get all exercises. If there is no exercises in local, it will get them from the server. + * @return a list of exercises + */ + async getExercises(): Promise<Exercise[]> { + if (!this.exercises.length) { + await this.fetchExercises(); + } + return this.exercises.map((e) => ExerciseMapperFrom(e)); + } + + /** + * Save an exercise. Could be a new one or an existing one. + * @param exercise the exercise to save + * @return either a response as a string or the saved exercise with their associated status code + */ + async saveExercise(exercise: Exercise): Promise<ResponseBody<string | IExercise>> { + const method = this.exercises.find((e) => e.id === exercise.id) ? 'PATCH' : 'POST'; + + const data: ResponseBody<string | IExercise> = (await $fetch(this.RESSOURCE, { + method: method, + body: JSON.stringify(exercise) + })); + + if (data.statusCode == 200) { + this.exercises = this.exercises.map((e) => e.id === exercise.id ? data.message as IExercise : e); + } else if (data.statusCode == 201) { + this.exercises.push(data.message as IExercise); + } + return data; + } + + /** + * Delete an exercise by its id + * @param id the id of the exercise to delete + * @return the response from the server + */ + async deleteExercise(id: number): Promise<ResponseBody<string>> { + const data: ResponseBody<string> = (await $fetch(`${this.RESSOURCE}?id=${id}`, { + method: 'DELETE' + })); + + if (data.statusCode == 200) { + this.exercises = this.exercises.filter((e) => e.id !== id); + } + return data; + } + + /** + * Fetch all sub-categories from the server + * @return a list of sub-categories + */ + async fetchSubCategories(): Promise<string[]> { + return (await $fetch(`${this.RESSOURCE}/fetch-sub-categories`, { + method: 'GET' + })).message ?? []; + } + + /** + * Get the next id for a new exercise. It is the maximum id + 1.<br> + * For example, if there are 3 ids : [1,3,7], the maximum id is 7 so the next id will be 8. + * @private + */ + private getNewId(): number { + return Math.max(...this.exercises.map((e) => e.id)) + 1; + } + + /** + * Fetch all exercises from the server + * @private + */ + private async fetchExercises(): Promise<void> { + this.exercises = (await $fetch(this.RESSOURCE, { + method: 'GET' + })).message ?? []; + } + +} + +export default ExerciseModule; \ No newline at end of file diff --git a/repository/module/user.ts b/repository/module/user.ts new file mode 100644 index 0000000..ae83479 --- /dev/null +++ b/repository/module/user.ts @@ -0,0 +1,21 @@ +import type {LoginBody} from "~/repository/model/LoginBody"; +import type {ResponseBody} from "~/types/ResponseBody"; +import type {IUser} from "~/repository/model/IUser"; + +class UserModule { + private readonly RESOURCE = '/api/auth'; + + /** + * Return the products as array + * @param body + * @returns + */ + async postLogin(body: LoginBody): Promise<ResponseBody<string | IUser>> { + return await $fetch(`${this.RESOURCE}/login`, { + method: 'POST', + body: JSON.stringify(body) + }) + } +} + +export default UserModule; \ No newline at end of file diff --git a/server/api/auth/login.post.ts b/server/api/auth/login.post.ts new file mode 100644 index 0000000..86114ac --- /dev/null +++ b/server/api/auth/login.post.ts @@ -0,0 +1,20 @@ +import {appendResponseHeader, defineEventHandler, readBody} from "h3"; +import {useRuntimeConfig} from "#imports"; + +export default defineEventHandler(async (event) => { + const url = event.path.replace('/api/', ''); + const config = useRuntimeConfig(event); + + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + headers.set('accept', '*/*'); + + const res = await fetch(`${config.public.apiBase}/${url}`, { + method: 'POST', + headers: headers, + body: JSON.stringify(await readBody(event)) + }) + + appendResponseHeader(event, 'set-cookie', res.headers.getSetCookie()) + return await res.json(); +}) \ No newline at end of file diff --git a/server/api/exercise/.delete.ts b/server/api/exercise/.delete.ts new file mode 100644 index 0000000..aaa9f5a --- /dev/null +++ b/server/api/exercise/.delete.ts @@ -0,0 +1,27 @@ +import {defineEventHandler} from "h3"; +import {useRuntimeConfig} from "#imports"; +import type {ResponseBody} from "~/types/ResponseBody"; + +// TODO : TU des différents retours de l'api +export default defineEventHandler(async (event) => { + const url = event.path.replace('/api/', ''); + const config = useRuntimeConfig(event); + + const headers = new Headers(); + headers.set('accept', '*/*'); + headers.set('Cookie', event.headers.get('Cookie') as string); + + const res = await fetch(`${config.public.apiBase}/${url}`, { + method: 'DELETE', + headers: headers + }) + + if (res.ok) { + return { + statusCode: res.status, + message: await res.text() + } as ResponseBody<string> + } else { + return await res.json() as ResponseBody<string> + } +}) \ No newline at end of file diff --git a/server/api/exercise/.get.ts b/server/api/exercise/.get.ts new file mode 100644 index 0000000..e1eda3d --- /dev/null +++ b/server/api/exercise/.get.ts @@ -0,0 +1,24 @@ +import {defineEventHandler} from "h3"; +import {useRuntimeConfig} from "#imports"; +import type {ResponseBody} from "~/types/ResponseBody"; +import type {IExercise} from "~/repository/model/IExercise"; + +// TODO : TU des différents retours de l'api +export default defineEventHandler(async (event) => { + const url = event.path.replace('/api/', ''); + const config = useRuntimeConfig(event); + + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + headers.set('accept', '*/*'); + + const res = await fetch(`${config.public.apiBase}/${url}`, { + method: 'GET', + headers: headers + }) + + return { + statusCode: res.status, + message: await res.json() + } as ResponseBody<IExercise[]>; +}) \ No newline at end of file diff --git a/server/api/exercise/.patch.ts b/server/api/exercise/.patch.ts new file mode 100644 index 0000000..7ae8d21 --- /dev/null +++ b/server/api/exercise/.patch.ts @@ -0,0 +1,45 @@ +import {defineEventHandler, readBody} from "h3"; +import {useRuntimeConfig} from "#imports"; +import type {ResponseBody} from "~/types/ResponseBody"; +import type {IExercise} from "~/repository/model/IExercise"; + +// TODO : TU des différents retours de l'api +export default defineEventHandler(async (event) => { + const url = event.path.replace('/api/', ''); + const config = useRuntimeConfig(event); + + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + headers.set('accept', '*/*'); + headers.set('Cookie', event.headers.get('Cookie') as string); + + const res = await fetch(`${config.public.apiBase}/${url}`, { + method: 'PATCH', + headers: headers, + body: ExerciseToPostBody(await readBody(event)) + }) + + if(res.ok) { + return { + statusCode: res.status, + message: await res.json() as IExercise + } as ResponseBody<IExercise> + } else { + return await res.json() as ResponseBody<string> + } +}) + +// TODO : TU +function ExerciseToPostBody(exercise: Exercise) { + return JSON.stringify({ + id: exercise.id, + name: exercise.name, + description: exercise.description, + category: exercise.category, + subCategory: exercise.subCategory, + safetyInstructions: exercise.safetyInstructions, + generalInstructions: exercise.generalInstructions, + diagram: exercise.diagram, + video: exercise.video + }); +} \ No newline at end of file diff --git a/server/api/exercise/.post.ts b/server/api/exercise/.post.ts new file mode 100644 index 0000000..2dc9762 --- /dev/null +++ b/server/api/exercise/.post.ts @@ -0,0 +1,45 @@ +import {defineEventHandler, readBody} from "h3"; +import {useRuntimeConfig} from "#imports"; +import type {ResponseBody} from "~/types/ResponseBody"; +import type {Exercise} from "~/types/Exercise"; +import type {IExercise} from "~/repository/model/IExercise"; + +// TODO : TU des différents retours de l'api +export default defineEventHandler(async (event) => { + const url = event.path.replace('/api/', ''); + const config = useRuntimeConfig(event); + + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + headers.set('accept', '*/*'); + headers.set('Cookie', event.headers.get('Cookie') as string); + + const res = await fetch(`${config.public.apiBase}/${url}`, { + method: 'POST', + headers: headers, + body: ExerciseToPostBody(await readBody(event)) + }) + + if(res.ok) { + return { + statusCode: res.status, + message: await res.json() as IExercise + } as ResponseBody<IExercise> + } else { + return await res.json() as ResponseBody<string> + } +}) + +// TODO : TU +function ExerciseToPostBody(exercise: Exercise) { + return JSON.stringify({ + name: exercise.name, + description: exercise.description, + category: exercise.category, + subCategory: exercise.subCategory, + safetyInstructions: exercise.safetyInstructions, + generalInstructions: exercise.generalInstructions, + diagram: exercise.diagram, + video: exercise.video + }); +} \ No newline at end of file diff --git a/server/api/exercise/fetch-sub-categories.get.ts b/server/api/exercise/fetch-sub-categories.get.ts new file mode 100644 index 0000000..892e83b --- /dev/null +++ b/server/api/exercise/fetch-sub-categories.get.ts @@ -0,0 +1,29 @@ +import {defineEventHandler} from "h3"; +import {useRuntimeConfig} from "#imports"; +import type {ResponseBody} from "~/types/ResponseBody"; + +export default defineEventHandler(async (event) => { + const url = event.path.replace('/api/', ''); + const config = useRuntimeConfig(event); + + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + headers.set('accept', '*/*'); + + const res = await fetch(`${config.public.apiBase}/${url}`, { + method: 'GET', + headers: headers + }) + + return { + statusCode: res.status, + message: mapSubCategoryToString(await res.json()) + } as ResponseBody<string[]>; +}) + +type ISubCategory = { + name: string; +} +function mapSubCategoryToString(categories: ISubCategory[]): string[] { + return categories.map(c => c.name); +} \ No newline at end of file diff --git a/types/Exercice.ts b/types/Exercice.ts deleted file mode 100644 index c0945ed..0000000 --- a/types/Exercice.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type {ExerciseCategory} from "~/types/enum/ExerciseCategory"; -import type {ExerciseSubCategory} from "~/types/enum/ExerciseSubCategory"; - -export default interface Exercise { - id: number; - name: string; - description: string; - category: ExerciseCategory; - subCategory?: ExerciseSubCategory; - safetyInstructions?: string[]; - generalInstructions?: string[]; - diagram?: string; - video?: string; - lat: number; - lng: number; -} \ No newline at end of file diff --git a/types/Exercise.ts b/types/Exercise.ts new file mode 100644 index 0000000..d6d3be0 --- /dev/null +++ b/types/Exercise.ts @@ -0,0 +1,33 @@ +import {ExerciseCategory} from "~/types/enum/ExerciseCategory"; + +export type Exercise = { + id: number; + name: string; + description: string; + category: ExerciseCategory; + subCategory: string; + safetyInstructions?: string; + generalInstructions?: string; + diagram?: string; + video?: string; + // FIXME: to remove + lat?: number; + lng?: number; + selected?: boolean; +}; + +export class ExerciseClass implements Exercise { + constructor(newId: number) { + this.id = newId; + } + + id: number; + name: string = ''; + description: string = ''; + safetyInstructions: string = ""; + generalInstructions: string = ""; + diagram: string = ''; + video: string = ''; + category: ExerciseCategory = ExerciseCategory.CARDIO; + subCategory: string = ''; +} \ No newline at end of file diff --git a/types/ResponseBody.ts b/types/ResponseBody.ts new file mode 100644 index 0000000..69deddd --- /dev/null +++ b/types/ResponseBody.ts @@ -0,0 +1,4 @@ +export type ResponseBody<T> = { + statusCode: number; + message: T; +} \ No newline at end of file diff --git a/types/enum/ExerciseCategory.ts b/types/enum/ExerciseCategory.ts index c405b4d..0e2f636 100644 --- a/types/enum/ExerciseCategory.ts +++ b/types/enum/ExerciseCategory.ts @@ -1,5 +1,41 @@ export enum ExerciseCategory { - RENFORCEMENT = 'Renforcement', + REINFORCEMENT = 'Reinforcement', CARDIO = 'Cardio', - STRETCHING = 'Stretching', + STRENGTHENING = 'Strengthening', +} + +/** + * Get the icon name for a given exercise category + * @param category the category of the exercise + * @return the icon name + */ +export function getExerciseCategoryIcon(category: ExerciseCategory): string { + switch (category) { + case ExerciseCategory.REINFORCEMENT: + return 'material-symbols:exercise-outline'; + case ExerciseCategory.CARDIO: + return 'material-symbols:cardiology-outline'; + case ExerciseCategory.STRENGTHENING: + return 'material-symbols:transfer-within-a-station'; + default: + return 'material-symbols:question-mark'; + } +} + +/** + * Translate an exercise category to French + * @param category the category of the exercise + * @return the translated category + */ +export function translateExerciseCategoryToFrench(category: ExerciseCategory): string { + switch (category) { + case ExerciseCategory.REINFORCEMENT: + return 'Renforcement musculaire'; + case ExerciseCategory.CARDIO: + return 'Cardio'; + case ExerciseCategory.STRENGTHENING: + return 'Étirement'; + default: + return 'Inconnu'; + } } \ No newline at end of file diff --git a/utils/mapper/ExerciseMapper.test.ts b/utils/mapper/ExerciseMapper.test.ts new file mode 100644 index 0000000..c6bb06b --- /dev/null +++ b/utils/mapper/ExerciseMapper.test.ts @@ -0,0 +1,105 @@ +import {expect, test} from 'vitest' +import {ExerciseMapperFrom, ExerciseMapperTo} from './ExerciseMapper' +import type {IExercise} from "~/repository/model/IExercise"; + +const iExercise: IExercise = { + id: 1, + name: 'name', + description: 'description', + safetyInstructions: 'safetyInstructions', + generalInstructions: 'generalInstructions', + diagram: 'diagram', + video: 'video', + category: { + name: 'Cardio' + }, + subCategory: { + name: 'subCategory' + } +} + +const exercise: Exercise = { + id: 1, + name: 'name', + description: 'description', + safetyInstructions: 'safetyInstructions', + generalInstructions: 'generalInstructions', + diagram: 'diagram', + video: 'video', + category: ExerciseCategory.CARDIO, + subCategory: 'subCategory', + selected: false +} + +// ExerciseMapperFrom +test('ExerciseMapperFrom', () => { + const mappedExercise = ExerciseMapperFrom(iExercise) + + expect(mappedExercise).toStrictEqual(exercise) +}) + +// ExerciseMapperTo +test('ExerciseMapperTo', () => { + const mappedIExercise = ExerciseMapperTo(exercise) + + expect(mappedIExercise).toStrictEqual(iExercise) +}) + +// ExerciseMapperFlipFlop +test('ExerciseMapperFlipFlop', () => { + const mappedIExercise = ExerciseMapperTo(ExerciseMapperFrom(iExercise)) + const mappedExercise = ExerciseMapperFrom(ExerciseMapperTo(exercise)) + + expect(mappedIExercise).toStrictEqual(iExercise) + expect(mappedExercise).toStrictEqual(exercise) +}) + +// ExerciseMapperFromNull, should throw an error +test('ExerciseMapperNull', () => { + expect(() => ExerciseMapperFrom(null)).toThrow(TypeError) + expect(() => ExerciseMapperTo(null)).toThrow(TypeError) +}) + +// ExerciseMapperFromEmptyOptional +test('ExerciseMapperEmptyOptional', () => { + const expected: Exercise = { + ...exercise, + safetyInstructions: '', + generalInstructions: '', + diagram: '', + video: '' + }; + + const toMap: IExercise = { + ...iExercise, + safetyInstructions: '', + generalInstructions: '', + diagram: '', + video: '' + } + + const mappedExercise = ExerciseMapperFrom(toMap) + expect(mappedExercise).toStrictEqual(expected) +}) + +// ExerciseMapperToEmptyOptional +test('ExerciseMapperToEmptyOptional', () => { + const expected: IExercise = { + ...iExercise, + safetyInstructions: '', + generalInstructions: '', + diagram: '', + video: '' + }; + + const toMap: Exercise = { + ...exercise, + safetyInstructions: '', + generalInstructions: '', + diagram: '', + video: '' + } + + const mappedIExercise = ExerciseMapperTo(toMap) + expect(mappedIExercise).toStrictEqual(expected) +}) \ No newline at end of file diff --git a/utils/mapper/ExerciseMapper.ts b/utils/mapper/ExerciseMapper.ts new file mode 100644 index 0000000..3da9e52 --- /dev/null +++ b/utils/mapper/ExerciseMapper.ts @@ -0,0 +1,35 @@ +import type {Exercise} from "~/types/Exercise"; +import type {IExercise} from "~/repository/model/IExercise"; + +export function ExerciseMapperFrom(iExercise: IExercise): Exercise { + return { + id: iExercise.id, + name: iExercise.name, + description: iExercise.description, + safetyInstructions: iExercise.safetyInstructions, + generalInstructions: iExercise.generalInstructions, + diagram: iExercise.diagram, + video: iExercise.video, + category: iExercise.category.name as ExerciseCategory, + subCategory: iExercise.subCategory?.name as ExerciseSubCategory, + selected: false + } +} + +export function ExerciseMapperTo(exercise: Exercise): IExercise { + return { + id: exercise.id, + name: exercise.name, + description: exercise.description, + safetyInstructions: exercise.safetyInstructions ?? '', + generalInstructions: exercise.generalInstructions ?? '', + diagram: exercise.diagram ?? '', + video: exercise.video ?? '', + category: { + name: exercise.category + }, + subCategory: { + name: exercise.subCategory ?? '' + } + } +} diff --git a/utils/mapper/UserMapper.ts b/utils/mapper/UserMapper.ts index 03f9ae2..7d91f70 100644 --- a/utils/mapper/UserMapper.ts +++ b/utils/mapper/UserMapper.ts @@ -1,7 +1,7 @@ import {Role} from "~/types/enum/Role"; import {Status} from "~/types/enum/Status"; -// FIXME : à déplacer dans un dossier DTO pour les retours de l'API +// FIXME : The APi doesn't return these data, currently only mocked interface UserDTO { id: number; lastname: string; -- GitLab