Parcourir la source

Merge branch 'release/0.3' into develop

Olivier Massot il y a 1 an
Parent
commit
05513ca080
59 fichiers modifiés avec 674 ajouts et 498 suppressions
  1. 6 3
      app.vue
  2. 8 4
      components/Common/ActionMenu.vue
  3. 25 0
      components/Common/AgendaLink.vue
  4. 8 1
      components/Common/Banner.vue
  5. 28 12
      components/Common/MenuScroll.vue
  6. 3 6
      components/Common/ReviewSection.vue
  7. 15 3
      components/Contact/Form.vue
  8. 0 60
      components/Form/Application.vue
  9. 4 4
      components/Home/Solution.vue
  10. 183 0
      components/JoinUs/Form.vue
  11. 7 3
      components/JoinUs/MissionDetail.vue
  12. 36 70
      components/JoinUs/Missions.vue
  13. 24 33
      components/Layout/AnchoredSection.vue
  14. 2 1
      components/Layout/Captcha.vue
  15. 3 3
      components/Layout/FAQ.vue
  16. 49 48
      components/Layout/Footer/Footer.vue
  17. 3 3
      components/Layout/Footer/Prefooter.vue
  18. 7 3
      components/Layout/Footer/Solutions.vue
  19. 16 8
      components/Layout/Navigation.vue
  20. 10 11
      components/Layout/Navigation/Topbar.vue
  21. 2 3
      components/Layout/Pagination.vue
  22. 5 2
      components/Logiciels/School/Contact.vue
  23. 4 0
      components/Logiciels/School/SomeNumbers.vue
  24. 3 2
      components/News/Details.vue
  25. 7 3
      components/News/List.vue
  26. 4 0
      env/.env.docker
  27. 3 0
      env/.env.prod
  28. 3 0
      env/.env.test
  29. 3 0
      env/.env.test1
  30. 3 0
      env/.env.test2
  31. 3 0
      env/.env.test3
  32. 3 0
      env/.env.test4
  33. 3 0
      env/.env.test5
  34. 3 3
      models/Maestro/ContactRequest.ts
  35. 35 0
      models/Maestro/JobApplication.ts
  36. 1 1
      models/Maestro/JobPosting.ts
  37. 17 0
      models/Maestro/Tag.ts
  38. 7 0
      nuxt.config.ts
  39. 18 40
      pages/CGV.vue
  40. 0 4
      pages/actualites/[id].vue
  41. 0 4
      pages/actualites/index.vue
  42. 0 4
      pages/formations.vue
  43. 0 3
      pages/index.vue
  44. 13 29
      pages/mentions-legales.vue
  45. 0 6
      pages/nous-contacter.vue
  46. 0 4
      pages/nous-rejoindre/[id].vue
  47. 0 4
      pages/nous-rejoindre/index.vue
  48. 3 6
      pages/opentalent_artist.vue
  49. 4 10
      pages/opentalent_manager.vue
  50. 2 6
      pages/opentalent_school.vue
  51. 0 0
      pages/poc.vue
  52. 19 44
      pages/politique-de-confidentialite-et-protection-des-donnees-personnelles.vue
  53. 1 5
      pages/qui-sommes-nous.vue
  54. 0 4
      pages/webinaires.vue
  55. 0 14
      plugins/router.ts
  56. 34 0
      services/utils/FileUtils.ts
  57. 2 1
      services/utils/dateUtils.ts
  58. 8 0
      stores/layoutStore.ts
  59. 24 20
      yarn.lock

+ 6 - 3
app.vue

@@ -1,9 +1,12 @@
 <template>
+  <div id="top" />
+
+  <LayoutNavigation />
+
   <nuxt-page />
+
+  <LayoutFooter />
 </template>
 
 <script setup lang="ts">
 </script>
-
-<!-- TODO: voir si possible de factoriser les consignes 'custom-row' un peu partout -->
-<!-- TODO: voir si possible d'utilser des sections pour factoriser les marges entre les sections d'une page -->

+ 8 - 4
components/Common/ActionMenu.vue

@@ -4,7 +4,7 @@ de l'écran (ou au bas de l'écran sur les petits écrans)
 -->
 <template>
   <!-- Écrans larges : menu lateral, accroché au bord droit de l'écran -->
-  <div class="sticky-menu lateral" v-if="lgAndUp">
+  <div class="sticky-menu lateral" v-if="lgAndUp && isVisible">
     <v-row
       v-for="(action, index) in actionsOrDefault"
       :key="index"
@@ -26,7 +26,7 @@ de l'écran (ou au bas de l'écran sur les petits écrans)
   </div>
 
   <!-- Petits écrans : menu sous forme de bandeau en pied de page (sauf si le footer du site est visible) -->
-  <div class="sticky-menu band" v-else-if="!layoutStore.isFooterVisible">
+  <div class="sticky-menu band" v-else-if="isVisible">
     <v-btn
       v-for="(action, index) in actionsOrDefault"
       :key="index"
@@ -43,15 +43,19 @@ import { useRouter } from "vue-router";
 import { useDisplay } from "vuetify";
 import { useLayoutStore } from "~/stores/layoutStore";
 import { ActionMenuItemType } from "~/types/enum/layout";
-import { ActionMenuItem } from "~/types/interface";
+import type { ActionMenuItem } from "~/types/interface";
 
-const { mdAndDown, lgAndUp } = useDisplay();
+const { lgAndUp } = useDisplay();
 const router = useRouter();
 const layoutStore = useLayoutStore()
 const { isMobileDevice } = useClientDevice()
 
 const telephoneNumber = "09 72 12 60 17";
 
+const isVisible: ComputedRef<boolean> = computed(() =>
+  !layoutStore.isHeaderVisible && !layoutStore.isFooterVisible
+)
+
 // Actions par défaut du menu, peut-être surchargé via la propriété `actions`
 const defaultActions: Array<ActionMenuItem> = [
   {

+ 25 - 0
components/Common/AgendaLink.vue

@@ -0,0 +1,25 @@
+<template>
+  <nuxt-link :href="target" target="_blank">
+    <slot />
+  </nuxt-link>
+</template>
+
+<script setup lang="ts">
+import UrlUtils from "~/services/utils/urlUtils";
+
+const runtimeConfig = useRuntimeConfig()
+
+const props = defineProps({
+  href: {
+    type: String
+  }
+})
+
+const target = computed(() => {
+  return UrlUtils.join(runtimeConfig.public.agendaBaseUrl as string, props.href as string)
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 8 - 1
components/Common/Banner.vue

@@ -6,7 +6,7 @@
           <img
             :src="imageSrc"
             :alt="imageAlt"
-            class="cover-image"
+            :class="['cover-image', reverseImage ? 'reverse' : '']"
           />
 
           <div v-if="logoSrc" class="details-square">
@@ -65,6 +65,10 @@ const props = defineProps({
   logoAltTheme: {
     type: Boolean,
     default: false
+  },
+  reverseImage: {
+    type: Boolean,
+    default: false
   }
 });
 </script>
@@ -95,6 +99,9 @@ const props = defineProps({
   object-position: center var(--banner-center-image);
   transition: transform 0.2s;
   margin: 0 auto;
+}
+
+.reverse {
   transform: scaleX(-1);
 }
 

+ 28 - 12
components/Common/MenuScroll.vue

@@ -1,7 +1,8 @@
-<!-- Menu sticky horizontal -->
-<!-- TODO: review -->
+<!--
+Menu de navigation entre les sections d'une page, accrochée au haut de l'écran sur les écrans larges
+-->
 <template>
-  <LayoutContainer>
+  <LayoutContainer v-scroll="handleScroll">
     <v-row>
       <v-col
         cols="12"
@@ -11,10 +12,18 @@
           density="compact"
           :class="{ 'sticky-menu': isSticky }"
         >
+          <nuxt-link
+            v-if="isSticky"
+            :to="{ path: '', hash: '#top' }"
+            class="px-3 py-1"
+          >
+            <v-icon icon="fa fa-angle-up" />
+          </nuxt-link>
+
           <div v-for="menu in menus" :key="menu.anchor">
             <nuxt-link :to="{ path: '', hash: '#' + menu.anchor }">
               <v-list-item
-                :class="{ active : isSticky && layoutStore.isAnchoredSectionOnScreen[menu.anchor] }"
+                :class="{ active : isSticky && menu.anchor === activeMenuItem }"
               >
                 {{ menu.label }}
               </v-list-item>
@@ -31,7 +40,7 @@ import type { PropType } from "@vue/runtime-core";
 import type { MenuScroll } from "~/types/interface";
 import { useLayoutStore } from "~/stores/layoutStore";
 
-const props = defineProps({
+defineProps({
   menus: {
     type: Array as PropType<Array < MenuScroll >>,
     required: true
@@ -42,8 +51,12 @@ const layoutStore = useLayoutStore()
 
 const isSticky: Ref<boolean> = ref(false);
 
-onMounted(() => {
-  window.addEventListener('scroll', handleScroll);
+const activeMenuItem: ComputedRef<string | null> = computed(() => {
+  return Object.entries(
+    layoutStore.isAnchoredSectionOnScreen
+  ).find(
+    ([key, value]) => value === true
+  )?.[0] ?? null
 })
 
 const handleScroll = () => {
@@ -64,10 +77,10 @@ const handleScroll = () => {
 .menu-container {
   z-index: 3;
   display: flex;
-  justify-content: space-around;
+  justify-content: center;
   background: var(--neutral-color);
-  color: var(--primary-color);
-  font-size: 1rem;
+  color: var(--on-neutral-color);
+  font-size: 15px;
   line-height: 19px;
   align-items: center;
   text-align: center;
@@ -77,7 +90,7 @@ const handleScroll = () => {
 
   a {
     text-decoration: none;
-    color: var(--primary-color);
+    color: var(--on-primary-color);
   }
 
   a:hover {
@@ -86,7 +99,10 @@ const handleScroll = () => {
 }
 
 .active {
-  font-weight: 800;
+  background-color: var(--on-primary-color);
+  color: var(--primary-color);
+  border-radius: 16px;
+  padding: 5px;
 }
 </style>
 

+ 3 - 6
components/Common/ReviewSection.vue

@@ -138,8 +138,7 @@ const goNext = () => {
 
   .v-card-text {
     text-align: justify;
-    min-height: 200px;
-    max-height: 200px;
+    min-height: 120px;
     overflow: auto;
     font-size: 0.9rem;
   }
@@ -150,7 +149,6 @@ const goNext = () => {
     flex-direction: column;
     justify-content: center;
     align-items: center;
-    margin-top: 1rem;
   }
 
   .v-card-actions {
@@ -158,7 +156,6 @@ const goNext = () => {
     font-size: 20px;
     line-height: 24px;
     color: var(--on-primary-color-alt) !important;
-    margin-top: 5rem;
     text-align: justify !important;
   }
 
@@ -198,8 +195,8 @@ const goNext = () => {
 .v-card {
   padding: 0 0.5rem;
   border-radius: 1rem;
-  min-height: 450px;
-  max-height: 450px;
+  min-height: 220px;
+  max-height: 250px;
   margin-top: 2rem;
   margin-bottom: 0.6rem;
 }

+ 15 - 3
components/Contact/Form.vue

@@ -124,9 +124,12 @@
           </v-col>
 
           <v-col cols="12" md="6">
-            <v-text-field
+            <v-select
               v-model="contactRequest.concernedProduct"
               label="Produit concerné (facultatif)"
+              :items="products"
+              item-value="id"
+              item-title="label"
             />
           </v-col>
         </v-row>
@@ -152,7 +155,7 @@
         <!-- Policy and  checkboxes -->
         <v-checkbox
           v-model="contactRequest.privacyPolicyAccepted"
-          :rules="[(v) => !!v || 'Vous devez accepter la politique de confidentialité']"
+          :rules="[(v: boolean) => v || 'Vous devez accepter la politique de confidentialité']"
           label="J'ai pris connaissance de la politique de confidentialité et j'accepte le traitement de mes données personnelles par Opentalent."
         />
 
@@ -217,18 +220,27 @@ const requestTypes: Array<{id: string, label: string}> = [
   { id: "CONTACT_REQUEST_OTHER", label: "Autre"}
 ]
 
+const products: Array<{id: string, label: string}> = [
+  { id: "PRODUCT_AGENDA", label: "Agenda culturel"},
+  { id: "PRODUCT_ARTIST", label: "Opentalent Artist"},
+  { id: "PRODUCT_SCHOOL", label: "Opentalent School"},
+  { id: "PRODUCT_MANAGER", label: "Opentalent Manager"},
+  { id: "PRODUCT_ADVERTISING", label: "Publicité"},
+  { id: "PRODUCT_OTHER", label: "Autre"}
+]
+
 const defaultRequestType = route.query.request ?? 'CONTACT_REQUEST_INFORMATION'
 
 //@ts-ignore
 const contactRequest: ContactRequest = reactive(em.newInstance(ContactRequest, { requestType: defaultRequestType }))
 
+// --- Validation ---
 const maxMessageLength = 2000
 
 const leftCars: ComputedRef<number> = computed(() =>
   maxMessageLength - (contactRequest.message ? contactRequest.message.length : 0)
 )
 
-// --- Validation ---
 const validateName = (name: string | null) => !!name || "Le nom est obligatoire";
 
 const validateSurname = (surname: string | null) => !!surname || "Le prénom est obligatoire";

+ 0 - 60
components/Form/Application.vue

@@ -1,60 +0,0 @@
-<!--
-TODO: n'est pas utilisé pour l'instant
--->
-
-<template>
-  <v-dialog v-model="dialog" persistent max-width="600">
-    <v-card>
-      <v-card-title>
-        Formulaire de Candidature
-      </v-card-title>
-      <v-card-text>
-        <!-- Ajoutez ici les champs du formulaire -->
-        <v-form @submit="submitApplication">
-          <v-text-field v-model="nom" label="Nom"></v-text-field>
-          <v-text-field v-model="prenom" label="Prénom"></v-text-field>
-          <v-text-field v-model="email" label="Email"></v-text-field>
-          <v-text-field v-model="telephone" label="Téléphone"></v-text-field>
-          <input type="file" @change="handleFileUpload" />
-          <v-textarea v-model="message" label="Message"></v-textarea>
-        </v-form>
-      </v-card-text>
-      <v-card-actions>
-        <v-btn @click="dialog = false">Annuler</v-btn>
-        <v-btn @click="submitApplication" color="primary">Envoyer</v-btn>
-      </v-card-actions>
-    </v-card>
-  </v-dialog>
-</template>
-
-<script>
-export default {
-  data() {
-    return {
-      dialog: false,
-      nom: "",
-      prenom: "",
-      email: "",
-      telephone: "",
-      cv: null,
-      lettreMotivation: null,
-      message: ""
-    };
-  },
-  methods: {
-    openApplicationForm() {
-      this.dialog = true;
-    },
-    handleFileUpload(event) {
-      const file = event.target.files[0];
-      if (file) {
-        // Gérer le fichier ici, par exemple
-      }
-    },
-    submitApplication() {
-      // Gérer l'envoi du formulaire ici
-      this.dialog = false;
-    }
-  }
-};
-</script>

+ 4 - 4
components/Home/Solution.vue

@@ -272,7 +272,7 @@ h3 {
   }
 
   .artist-image {
-    background: url(/images/solutions/artist.jpg);
+    background-image: url(/images/solutions/artist.jpg);
   }
 
   .artist-image:hover::before {
@@ -280,8 +280,8 @@ h3 {
     cursor: pointer;
   }
 
-  .school-image{
-    background: url(/images/solutions/school.jpg);
+  .school-image {
+    background-image: url(/images/solutions/school.jpg);
   }
 
   .school-image:hover::before {
@@ -290,7 +290,7 @@ h3 {
   }
 
   .manager-image {
-    background: url(/images/solutions/manager.png);
+    background-image: url(/images/solutions/manager.png);
   }
 
   .manager-image:hover::before {

+ 183 - 0
components/JoinUs/Form.vue

@@ -0,0 +1,183 @@
+<template>
+  <div>
+    <v-card
+      v-if="!jobApplicationSent"
+    >
+      <v-card-title
+        class="text-center"
+      >
+        Formulaire de Candidature
+      </v-card-title>
+
+      <v-card-text>
+        <v-form
+          ref="form"
+          validate-on="submit lazy"
+          @submit.prevent="submit"
+        >
+          <v-text-field
+            id="jobApplicationName"
+            v-model="jobApplication.name"
+            :rules="[validateName]"
+            label="Nom*"
+            required
+          />
+
+          <v-text-field
+            id="jobApplicationSurname"
+            v-model="jobApplication.surname"
+            :rules="[validateSurname]"
+            label="Prénom*"
+            required
+          />
+
+          <v-text-field
+            id="jobApplicationPhone"
+            v-model="jobApplication.phone"
+            :rules="[validatePhone]"
+            label="Téléphone*"
+            required
+          />
+
+          <v-text-field
+            id="jobApplicationEmail"
+            v-model="jobApplication.email"
+            :rules="[validateEmail]"
+            label="Email*"
+            required
+
+          />
+
+          <v-file-input
+            id="jobApplicationResume"
+            v-model="resumeUpload"
+            :rules="[validateResume]"
+            label="Dépôt de CV*"
+            accept=".pdf, .jpeg, .png"
+            required
+          />
+
+          <v-file-input
+            id="jobApplicationMotivationLetter"
+            v-model="motivationLetterUpload"
+            label="Dépôt de lettre de motivation"
+            accept=".pdf, .jpeg, .png"
+          />
+
+          <v-textarea
+            id="jobApplicationMessage"
+            v-model="jobApplication.message"
+            :rules="[validateNonEmptyMessage, validateMessageLength]"
+            label="Message*"
+            required
+          />
+          <span class="remaining-cars-notice">{{ leftCars }} caractères restants</span>
+
+          <div class="d-flex flex-column align-center mt-4">
+            <!-- @see https://github.com/hCaptcha/vue-hcaptcha -->
+            <LayoutCaptcha/>
+          </div>
+        </v-form>
+      </v-card-text>
+
+      <p class="text-right mr-6">
+        * Champs obligatoires
+      </p>
+
+      <v-card-actions class="justify-center">
+        <v-btn
+          class="btn-more mb-4"
+          @click="submit"
+        >
+          Envoyer
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ContactRequest from "~/models/Maestro/ContactRequest";
+import { useEntityManager } from "~/composables/data/useEntityManager";
+import JobApplication from "~/models/Maestro/JobApplication";
+import FileUtils from "~/services/utils/FileUtils";
+
+const { em } = useEntityManager()
+
+const form: Ref<any | null> = ref(null)
+
+const jobApplicationSent: Ref<boolean> = ref(false)
+
+const emit = defineEmits(['submit'])
+
+//@ts-ignore
+const jobApplication: ContactRequest = reactive(em.newInstance(JobApplication))
+
+const resumeUpload = ref(null)
+
+const motivationLetterUpload = ref(null)
+
+// --- Validation ---
+const maxMessageLength = 2000
+
+const leftCars: ComputedRef<number> = computed(() =>
+  maxMessageLength - (jobApplication.message ? jobApplication.message.length : 0)
+)
+
+const validateName = (name: string | null) => !!name || "Le nom est obligatoire";
+
+const validateSurname = (surname: string | null) => !!surname || "Le prénom est obligatoire";
+
+const validateEmail = (email: string | null) =>
+  (!!email && /.+@.+\..+/.test(email)) || "L'adresse e-mail doit être valide";
+
+const validatePhone = (email: string | null) =>
+  (!!email && /^((\+|00)33\s?|0)[1-7]([\s.]?\d{2}){4}$/.test(email)) || "Le numéro de téléphone doit être valide";
+
+const validateResume = (surname: string | null) =>
+  resumeUpload.value !== null && resumeUpload.value[0] !== null || "Vous devez joindre un CV à l'un des formats indiqués";
+
+const validateNonEmptyMessage = (message: string | null) =>
+  (!!message && message.length > 0) ||
+  "Le message ne peut pas être vide";
+
+const validateMessageLength = async (message: string | null) =>
+  (!!message && message.length <= maxMessageLength) ||
+  "Le message ne doit pas dépasser " + maxMessageLength + " caractères";
+
+/**
+ * Soumet le formulaire de candidature (boite de dialogue)
+ */
+const submit = async () => {
+  const { valid } = await form.value.validate()
+
+  if (!valid) {
+    jobApplicationSent.value = false
+    return
+  }
+
+  jobApplication.resume = (resumeUpload.value !== null && resumeUpload.value[0] !== null) ?
+    {
+      //@ts-ignore
+      'name': resumeUpload.value[0].name,
+      'content': await FileUtils.blobToBase64(resumeUpload.value[0])
+    } : null
+
+  jobApplication.motivationLetter = (motivationLetterUpload.value !== null && motivationLetterUpload.value[0] !== null) ?
+    {
+      //@ts-ignore
+      'name': motivationLetterUpload.value[0].name,
+      'content': await FileUtils.blobToBase64(motivationLetterUpload.value[0])
+    } : null
+
+  await em.persist(JobApplication, jobApplication)
+
+  jobApplicationSent.value = true;
+
+  emit('submit')
+};
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 7 - 3
components/JoinUs/MissionDetail.vue

@@ -89,8 +89,9 @@
 
           <v-row class="d-flex justify-space-between mb-8 center-90">
             <p class="key-word mt-3">
-              <!-- TODO: remplacer par la bonne prop -->
-              ROCK CONCERT FESTIVAL
+              <span v-for="tag in job.tags" class="mr-2">
+                {{ tag.name }}
+              </span>
             </p>
 
             <CommonShare />
@@ -110,7 +111,6 @@ import DateUtils from "~/services/utils/dateUtils";
 
 const route = useRoute();
 const { fetch } = useEntityFetch()
-const config = useRuntimeConfig()
 
 const jobId: number = parseInt(route.params.id as string)
 if (!jobId || isNaN(jobId)) {
@@ -120,6 +120,10 @@ if (!jobId || isNaN(jobId)) {
 const { data: job, pending } = fetch(JobPosting, jobId)
 
 const formatDate = (date: string) => {
+  console.log(date)
+  if (!date) {
+    return "-"
+  }
   return DateUtils.format(new Date(date), "dd/MM/yyyy")
 };
 </script>

+ 36 - 70
components/JoinUs/Missions.vue

@@ -80,67 +80,27 @@
     <v-dialog
       v-model="dialog"
       max-width="600px"
+      :persistent="!jobApplicationSent"
+      no-click-animation
+      :retain-focus="false"
     >
-      <v-card>
-        <v-card-title
-          class="text-center"
-        >
-          Formulaire de Candidature
-        </v-card-title>
-
-        <v-card-text>
-          <v-form>
-            <v-text-field
-              label="Nom*"
-              required
-            />
-
-            <v-text-field
-              label="Prénom*"
-              required
-            />
-
-            <v-text-field
-              label="Téléphone*"
-              required
-            />
-
-            <v-text-field
-              label="Email*"
-              required
-            />
-
-            <v-file-input
-              label="Dépôt de CV*"
-              accept=".pdf, .jpeg, .png"
-              required
-            />
-
-            <v-file-input
-              label="Dépôt de lettre de motivation"
-              accept=".pdf, .jpeg, .png"
-            />
-
-            <v-textarea
-              label="Message*"
-              required
-            />
-          </v-form>
-        </v-card-text>
-
-        <p class="text-right mr-6">
-          * Champs obligatoires
-        </p>
+      <div v-if="!jobApplicationSent">
+        <JoinUsForm @submit="onFormSubmit"/>
 
-        <v-card-actions class="justify-center">
-          <v-btn
-            class="btn-more mb-4"
-            @click="sendApplication"
-          >
-            Envoyer
-          </v-btn>
-        </v-card-actions>
-      </v-card>
+        <v-btn @click="dialog = false">
+          Annuler
+        </v-btn>
+      </div>
+      <div v-else>
+        <v-card class="pa-6 text-center">
+          Votre candidature a bien été envoyée, merci de votre intérêt.<br/>
+          Nous vous recontacterons dès que possible.
+        </v-card>
+
+        <v-btn @click="dialog = false">
+          Fermer
+        </v-btn>
+      </div>
     </v-dialog>
   </LayoutContainer>
 </template>
@@ -150,8 +110,6 @@ import { useEntityFetch } from "~/composables/data/useEntityFetch";
 import JobPosting from "~/models/Maestro/JobPosting";
 import type { AnyJson } from "~/types/data";
 
-const i18n = useI18n();
-const router = useRouter()
 const { fetchCollection } = useEntityFetch()
 
 const page: Ref<number> = ref(1);
@@ -191,14 +149,11 @@ const onPageUpdated = async (newVal: number): Promise<void> => {
  * Faut-il afficher la boite de dialogue de candidature
  */
 const dialog = ref(false);
+const jobApplicationSent: Ref<boolean> = ref(false)
 
-/**
- * Soumet le formulaire de candidature (boite de dialogue)
- */
-const sendApplication = () => {
-  // TODO: implémenter le submit
-  dialog.value = false;
-};
+const onFormSubmit = () => {
+  jobApplicationSent.value = true
+}
 </script>
 
 <style scoped lang="scss">
@@ -296,8 +251,19 @@ const sendApplication = () => {
 }
 
 .v-dialog {
-  .btn-more {
-    width: 128px;
+  :deep(.v-overlay__content) {
+    overflow: auto !important;
+  }
+
+  :deep(.v-card) {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+
+  .v-btn {
+    width: 100%;
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
   }
 }
 </style>

+ 24 - 33
components/Layout/AnchoredSection.vue

@@ -1,13 +1,11 @@
 <template>
-  <!--suppress VueUnrecognizedDirective -->
   <div
     :id="id"
     ref="section"
-    v-intersect="onIntersect"
+    v-scroll="onScroll"
   >
     <slot/>
   </div>
-
 </template>
 
 <script setup lang="ts">
@@ -30,38 +28,31 @@
 
   layoutStore.setIsAnchoredSectionOnScreen(props.id, false)
 
-  const onIntersect = (e: boolean) => {
-    layoutStore.setIsAnchoredSectionOnScreen(props.id, e)
-  }
+  const top: Ref<number | null> = ref(null)
+  const bottom: Ref<number | null> = ref(null)
+
+  onMounted(() => {
+    top.value = section.value!.offsetTop
+    bottom.value = section.value!.offsetTop + section.value!.offsetHeight
+  })
+
+  const onScroll = (e: any) => {
+    if (top.value === null || bottom.value === null) {
+      return
+    }
 
-  // TODO: supprimer si pas nécessaire, je conserve au cas où il faudrait un
-  //       fonctionnement plus fin qu'un simple intersect
-  // const top: Ref<number | null> = ref(null)
-  // const bottom: Ref<number | null> = ref(null)
-  //
-  // onMounted(() => {
-  //   top.value = section.value!.offsetTop
-  //   bottom.value = section.value!.offsetTop + section.value!.offsetHeight
-  // })
-  //
-  // const onScroll = (e: any) => {
-  //   if (top.value === null || bottom.value === null) {
-  //     return
-  //   }
-  //
-  //   const scroll: number = e.target.scrollingElement.scrollTop
-  //
-  //   const active = scroll > top.value && scroll < bottom.value
-  //
-  //   if (active !== layoutStore.isAnchoredSectionOnScreen[props.id]) {
-  //     layoutStore.setIsAnchoredSectionOnScreen(
-  //       props.id,
-  //       active
-  //     )
-  //   }
-  // }
+    const screenVerticalCenter = document.documentElement.scrollTop + window.innerHeight / 2
+
+    const active = screenVerticalCenter > top.value && screenVerticalCenter < bottom.value
+
+    if (active !== layoutStore.isAnchoredSectionOnScreen[props.id]) {
+        layoutStore.setIsAnchoredSectionOnScreen(
+          props.id,
+          active
+        )
+    }
+  }
 </script>
 
 <style scoped>
-
 </style>

+ 2 - 1
components/Layout/Captcha.vue

@@ -1,6 +1,6 @@
 <template>
   <vue-hcaptcha
-    :sitekey="runtimeConfig.hCaptchaSiteKey"
+    :sitekey="siteKey"
     @verify="onVerify"
     @expired="onExpire"
     @challenge-expired="onChallengeExpire"
@@ -17,6 +17,7 @@
 import VueHcaptcha from "@hcaptcha/vue3-hcaptcha";
 
 const runtimeConfig = useRuntimeConfig()
+const siteKey = runtimeConfig.public.hCaptchaSiteKey
 
 const verified: Ref<boolean> = ref(false);
 const expired: Ref<boolean> = ref(false);

+ 3 - 3
components/Layout/FAQ.vue

@@ -12,7 +12,7 @@
         </h3>
 
         <v-btn
-          to="https://ressources.opentalent.fr/"
+          href="https://ressources.opentalent.fr/"
           target="_blank"
           class="btn-faq inv-theme"
         >
@@ -22,7 +22,7 @@
 
       <div class="d-flex flex-column justify-center links">
         <v-btn
-          to="https://ressources.opentalent.fr/pages/viewpage.action?pageId=75170564"
+          href="https://ressources.opentalent.fr/pages/viewpage.action?pageId=75170564"
           target="_blank"
         >
           <div>
@@ -37,7 +37,7 @@
         </v-btn>
 
         <v-btn
-          to="https://ressources.opentalent.fr/?contact"
+          href="https://ressources.opentalent.fr/?contact"
           target="_blank"
         >
           <div>

+ 49 - 48
components/Layout/Footer/Footer.vue

@@ -19,19 +19,19 @@
               </h5>
             </v-row>
             <v-row>
-              <nuxt-link to="/annuaire">
+              <AgendaLink href="/annuaire">
                 Annuaire
-              </nuxt-link>
+              </AgendaLink>
             </v-row>
             <v-row>
-              <nuxt-link to="/actualites">
+              <AgendaLink href="/actualites">
                 Actualités
-              </nuxt-link>
+              </AgendaLink>
             </v-row>
             <v-row>
-              <nuxt-link to="/annonces">
+              <AgendaLink href="/annonces">
                 Annonces
-              </nuxt-link>
+              </AgendaLink>
             </v-row>
           </v-col>
 
@@ -85,19 +85,19 @@
           </v-col>
 
           <!-- Cinquième section : liens réseaux sociaux (écrans larges seulement) -->
-          <v-col v-if="mdAndUp" cols="3">
-            <v-row>
+          <v-col v-if="mdAndUp" cols="2">
+            <v-row class="justify-center">
               <h5>
                 Suivez-nous
               </h5>
             </v-row>
 
-            <v-row class="social-networks">
+            <v-row class="justify-center social-networks">
               <v-col cols="2">
                 <nuxt-link
                   href="https://www.facebook.com/opentalent"
                   target="_blank"
-                  class="fab fa-facebook"
+                  class="fab fa-square-facebook"
                 />
               </v-col>
               <v-col cols="2">
@@ -119,7 +119,7 @@
                 <nuxt-link
                   href="https://www.youtube.com/@Opentalent74300"
                   target="_blank"
-                  class="fab fa-youtube"
+                  class="fab fa-square-youtube"
                 />
               </v-col>
             </v-row>
@@ -136,12 +136,12 @@
             <nuxt-link
               href="https://www.facebook.com/opentalent"
               target="_blank"
-              class="fab fa-facebook"
+              class="fab fa-square-facebook"
             />
             <nuxt-link
               href="https://twitter.com/Opentalent_FRA"
               target="_blank"
-              class="fa-brands fa-square-twitter"
+              class="fa-brands fa-square-x-twitter"
             />
             <nuxt-link
               href="https://www.linkedin.com/company/2iopenservice"
@@ -151,42 +151,42 @@
             <nuxt-link
               href="https://www.youtube.com/@Opentalent74300"
               target="_blank"
-              class="fab fa-youtube"
+              class="fab fa-square-youtube"
             />
           </v-col>
         </v-row>
       </div>
 
       <!-- Troisième section alt : version petits écrans -->
-      <v-row v-if="mdAndDown">
-        <v-col cols="12" >
-          <div v-for="(item, index) in footerLinks" :key="index">
-            <v-container>
-              <div class="section" @click="toggleSection(index)">
-                <div class="d-flex flex-row justify-space-between">
-                  {{ item.label }}
-
-                  <v-icon
-                    :icon="isActive(index) ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"
-                  />
-                </div>
-
-                <div
-                  v-show="isActive(index)"
-                  v-for="(sublink, sublinkIndex) in item.sublink"
-                  :key="sublinkIndex"
-                  class="mt-3"
-                >
-                  <nuxt-link :to="sublink.link">
-                    {{ sublink.label }}
-                  </nuxt-link>
-                </div>
-              </div>
-            </v-container>
-
-          </div>
-        </v-col>
-      </v-row>
+<!--      <v-row v-if="mdAndDown">-->
+<!--        <v-col cols="12" >-->
+<!--          <div v-for="(item, index) in footerLinks" :key="index">-->
+<!--            <v-container>-->
+<!--              <div class="section" @click="toggleSection(index)">-->
+<!--                <div class="d-flex flex-row justify-space-between">-->
+<!--                  {{ item.label }}-->
+
+<!--                  <v-icon-->
+<!--                    :icon="isActive(index) ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"-->
+<!--                  />-->
+<!--                </div>-->
+
+<!--                <div-->
+<!--                  v-show="isActive(index)"-->
+<!--                  v-for="(sublink, sublinkIndex) in item.sublink"-->
+<!--                  :key="sublinkIndex"-->
+<!--                  class="mt-3"-->
+<!--                >-->
+<!--                  <nuxt-link :href="sublink.link">-->
+<!--                    {{ sublink.label }}-->
+<!--                  </nuxt-link>-->
+<!--                </div>-->
+<!--              </div>-->
+<!--            </v-container>-->
+
+<!--          </div>-->
+<!--        </v-col>-->
+<!--      </v-row>-->
 
       <div class="footnotes">
         <v-row justify="center">
@@ -218,6 +218,7 @@
 <script setup>
 import { useDisplay } from "vuetify";
 import { useLayoutStore } from "~/stores/layoutStore";
+import AgendaLink from "~/components/Common/AgendaLink.vue";
 
 
 const { mdAndDown, mdAndUp } = useDisplay();
@@ -245,7 +246,7 @@ const footerLinks = ref([
     sublink: [
       {
         label: "Opentalent Artist",
-        link: "/opentalent_artist)",
+        link: "/opentalent_artist",
       },
       {
         label: "Opentalent School",
@@ -262,7 +263,7 @@ const footerLinks = ref([
     sublink: [
       {
         label: "Qui sommes-nous",
-        link: "/qui-sommes-nous)",
+        link: "/qui-sommes-nous",
       },
       {
         label: "Nous rejoindre",
@@ -279,7 +280,7 @@ const footerLinks = ref([
     sublink: [
       {
         label: "Foire Aux Questions ",
-        link: "/ https://ressources.opentalent.fr/)",
+        link: "/ https://ressources.opentalent.fr",
       },
       {
         label: "Support en ligne ",
@@ -318,7 +319,8 @@ const onIntersect = (isIntersecting) => {
 
 .logo .v-img {
   margin-bottom: 3rem;
-  width: 25rem;
+  width: 300px;
+  height: 100px;
 }
 
 .v-col {
@@ -357,7 +359,6 @@ a {
 }
 
 .footnotes {
-  margin-top: 2rem;
   border-top: 0.4px solid var(--neutral-color-alt-strong);
   box-shadow: 0 3px 24px rgba(0, 0, 0, 0.07);
 

+ 3 - 3
components/Layout/Footer/Prefooter.vue

@@ -8,7 +8,7 @@ Première section du footer (galerie des logiciels)
       <v-row>
 
         <v-col cols="4" class="text-center">
-          <nuxt-link href="/opentalent_artist">
+          <nuxt-link to="/opentalent_artist">
             <v-img src="/images/logo/logiciels/Artist-noir.png"/>
           </nuxt-link>
 
@@ -20,7 +20,7 @@ Première section du footer (galerie des logiciels)
         <v-divider :vertical="true" />
 
         <v-col cols="4" class="text-center">
-          <nuxt-link href="/opentalent_school">
+          <nuxt-link to="/opentalent_school">
             <v-img src="/images/logo/logiciels/School-noir.png"/>
           </nuxt-link>
 
@@ -32,7 +32,7 @@ Première section du footer (galerie des logiciels)
         <v-divider :vertical="true" />
 
         <v-col cols="3" class="text-center">
-          <nuxt-link href="/opentalent_manager">
+          <nuxt-link to="/opentalent_manager">
             <v-img src="/images/logo/logiciels/Manager-noir.png"/>
           </nuxt-link>
 

+ 7 - 3
components/Layout/Footer/Solutions.vue

@@ -1,11 +1,15 @@
 <template>
   <div id="solutions">
     <LayoutContainer>
-      <v-row>
+      <v-row class="center-90">
         <v-col cols="12">
-          <h4 class="solution-title text-center">
+          <LayoutUISubTitle>
+            NOS LOGICIELS OPENTALENT
+          </LayoutUISubTitle>
+
+          <LayoutUITitle>
             Ces solutions peuvent aussi vous intéresser
-          </h4>
+          </LayoutUITitle>
         </v-col>
       </v-row>
 

+ 16 - 8
components/Layout/Navigation.vue

@@ -2,21 +2,20 @@
 Menu Navigation
 -->
 <template>
-
   <!-- Navigation (écran large) -->
-  <div v-if="mdAndUp">
+  <div v-if="mdAndUp" v-intersect="onIntersect">
     <LayoutNavigationTopbar />
 
     <v-row class="navigation-lg" style="margin-top: 0 !important;">
       <!-- Logo Opentalent -->
-      <v-col cols="3">
+      <v-col cols="2">
         <nuxt-link to="/">
           <v-img class="logo" src="/images/logo/navigation-logo.png" />
         </nuxt-link>
       </v-col>
 
       <!-- Menu principal -->
-      <v-col cols="9">
+      <v-col cols="10" class="pl-6">
         <v-menu
           v-for="item in menu"
           :key="item.label"
@@ -78,11 +77,11 @@ Menu Navigation
         <v-icon left class="fas fa-phone icon"></v-icon>
       </v-btn>
 
-      <nuxt-link to="/agenda-culturel" style="text-decoration: none">
+      <AgendaLink href="/agenda-culturel" style="text-decoration: none">
         <v-btn text>
           <v-icon left class="fas fa-calendar icon"></v-icon>
         </v-btn>
-      </nuxt-link>
+      </AgendaLink>
     </v-app-bar>
 
     <!-- Tiroir de navigation principal -->
@@ -121,6 +120,9 @@ Menu Navigation
 <script setup lang="ts">
 import { useDisplay } from "vuetify";
 import type { MainMenuItem } from "~/types/interface";
+import AgendaLink from "~/components/Common/AgendaLink.vue";
+import Footer from "~/components/Layout/Footer/Footer.vue";
+import { useLayoutStore } from "~/stores/layoutStore";
 const { mdAndUp } = useDisplay();
 
 const menu: Array<MainMenuItem> = [
@@ -201,12 +203,18 @@ const withAnimation = (callback: () => void) => {
   setTimeout(() => {isMenuOpen.value = true}, 85)
 }
 
+const layoutStore = useLayoutStore()
+const onIntersect = (isIntersecting: boolean) => {
+  layoutStore.setIsHeaderVisible(isIntersecting)
+}
+
 </script>
 
 <style scoped>
 
 .logo {
-  height: 8rem;
+  height: 100px;
+  width: 300px;
 }
 
 .logo-md {
@@ -241,7 +249,7 @@ const withAnimation = (callback: () => void) => {
   }
 
   .menuItem.first-level {
-    font-size: 1rem;
+    font-size: 1.3rem;
     margin-right: 1rem;
     font-weight: 700;
     letter-spacing: 0.05em;

+ 10 - 11
components/Layout/Navigation/Topbar.vue

@@ -5,23 +5,22 @@
         prepend-icon="fas fa-user"
         class="btn-login"
       >
-        Se connecter<br />aux logiciels
+        Se connecter
       </v-btn>
 
-      <v-btn
-        to="/agenda-culturel"
-        prepend-icon="fas fa-calendar"
-        class="btn-agenda"
-      >
-          Agenda Culturel
-      </v-btn>
+      <AgendaLink href="/">
+        <v-btn
+          prepend-icon="fas fa-calendar"
+          class="btn-agenda"
+        >
+            Agenda Culturel
+        </v-btn>
+      </AgendaLink>
   </div>
 </template>
 
 <script setup lang="ts">
-import { useDisplay } from "vuetify";
-
-const { mdAndDown } = useDisplay();
+import AgendaLink from "~/components/Common/AgendaLink.vue";
 </script>
 
 <style scoped lang="scss">

+ 2 - 3
components/Layout/Pagination.vue

@@ -9,9 +9,8 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, defineEmits, ComputedRef } from "vue";
-import { PropType } from "@vue/runtime-core";
-import { Pagination } from "~/types/data";
+import type { PropType } from "@vue/runtime-core";
+import type { Pagination } from "~/types/data";
 
 const props = defineProps({
   modelValue: {

+ 5 - 2
components/Logiciels/School/Contact.vue

@@ -66,8 +66,11 @@
               obtenir vos codes d’accès.
             </p>
 
-            <!-- TODO: manque le @click -->
-            <v-btn class="btn-contact">
+            <v-btn
+              href="https://www.cmf-musique.org/contact/"
+              target="_blank"
+              class="btn-contact"
+            >
               Je souhaite obtenir mon code d'accès
             </v-btn>
           </div>

+ 4 - 0
components/Logiciels/School/SomeNumbers.vue

@@ -76,6 +76,10 @@ const items: Array<{ src: string }> = [
 </script>
 
 <style scoped lang="scss">
+.v-container {
+  --on-primary-color-alt: var(--secondary-color);
+}
+
 .custom-row {
   width: 90%;
   margin-right: auto;

+ 3 - 2
components/News/Details.vue

@@ -70,8 +70,9 @@
 
           <v-row class="d-flex justify-space-between mb-8 center-90">
             <p class="key-word mt-3">
-              <!-- TODO: remplacer par la bonne prop -->
-              ROCK CONCERT FESTIVAL
+              <span v-for="tag in newsItem.tags" class="mr-2">
+                {{ tag.name }}
+              </span>
             </p>
 
             <CommonShare />

+ 7 - 3
components/News/List.vue

@@ -12,6 +12,12 @@
     </v-row>
   </div>
 
+  <div v-else-if="!newsCollection || !(newsCollection!.items)">
+    <v-row class="justify-center">
+      Aucun résultat trouvé
+    </v-row>
+  </div>
+
   <div v-else>
     <v-row
       v-for="(newsItem, index) in newsCollection!.items"
@@ -82,7 +88,7 @@
       </v-card>
     </v-row>
 
-    <v-row>
+    <v-row v-if="newsCollection!.items">
       <v-col cols="12">
         <LayoutPagination
           v-if="newsCollection && newsCollection.pagination"
@@ -97,8 +103,6 @@
 </template>
 
 <script setup lang="ts">
-
-import { ComputedRef } from "vue";
 import { useEntityFetch } from "~/composables/data/useEntityFetch";
 import News from "~/models/Maestro/News";
 

+ 4 - 0
env/.env.docker

@@ -5,3 +5,7 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://nginx_maestro
 NUXT_PUBLIC_API_BASE_URL=https://local.maestro.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://local.agenda.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://local.agenda.opentalent.fr
+

+ 3 - 0
env/.env.prod

@@ -2,3 +2,6 @@
 # /!\ -- USE ONLY IN PRODUCTION --
 NUXT_ENV=production
 NUXT_DEBUG=0
+
+NUXT_AGENDA_BASE_URL=https://opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://opentalent.fr

+ 3 - 0
env/.env.test

@@ -4,3 +4,6 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://api.test.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://api.test.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://agenda.test.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test.opentalent.fr

+ 3 - 0
env/.env.test1

@@ -4,3 +4,6 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://api.test1.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://api.test1.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://agenda.test1.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test1.opentalent.fr

+ 3 - 0
env/.env.test2

@@ -4,3 +4,6 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://api.test2.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://api.test2.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://agenda.test2.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test2.opentalent.fr

+ 3 - 0
env/.env.test3

@@ -4,3 +4,6 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://api.test3.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://api.test3.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://agenda.test3.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test3.opentalent.fr

+ 3 - 0
env/.env.test4

@@ -4,3 +4,6 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://api.test4.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://api.test4.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://agenda.test4.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test4.opentalent.fr

+ 3 - 0
env/.env.test5

@@ -4,3 +4,6 @@ DEBUG=1
 
 NUXT_API_BASE_URL=https://api.test5.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://api.test5.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://agenda.test5.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test5.opentalent.fr

+ 3 - 3
models/Maestro/ContactRequest.ts

@@ -2,12 +2,12 @@ import ApiModel from "~/models/ApiModel";
 import {Uid, Str, Bool, Attr} from "pinia-orm/dist/decorators";
 
 /**
- * Maestro Model : JobPosting
+ * Maestro Model : ContactRequest
  *
- * @see https://gitlab.2iopenservice.com/opentalent/maestro/-/blob/master/src/Entity/JobPosting/JobPosting.php?ref_type=heads
+ * @see https://gitlab.2iopenservice.com/opentalent/maestro/-/blob/master/src/ApiResource/ContactRequest.php?ref_type=heads
  */
 export default class ContactRequest extends ApiModel {
-  static entity = 'contact-request'
+  static entity = 'contact_request'
 
   @Uid()
   declare id: number

+ 35 - 0
models/Maestro/JobApplication.ts

@@ -0,0 +1,35 @@
+import ApiModel from "~/models/ApiModel";
+import {Uid, Str, Bool, Attr} from "pinia-orm/dist/decorators";
+
+/**
+ * Maestro Model : JobApplication
+ *
+ * @see https://gitlab.2iopenservice.com/opentalent/maestro/-/blob/master/src/ApiResources/JobApplication.php?ref_type=heads
+ */
+export default class JobApplication extends ApiModel {
+  static entity = 'job_application'
+
+  @Uid()
+  declare id: number
+
+  @Str(null)
+  declare name: string | null
+
+  @Str(null)
+  declare surname: string | null
+
+  @Str(null)
+  declare phone: string | null
+
+  @Str(null)
+  declare email: string | null
+
+  @Attr(null)
+  declare resume: object | null
+
+  @Attr(null)
+  declare motivationLetter: object | null
+
+  @Str(null)
+  declare message: string | null
+}

+ 1 - 1
models/Maestro/JobPosting.ts

@@ -7,7 +7,7 @@ import {Uid, Str, Bool, Attr} from "pinia-orm/dist/decorators";
  * @see https://gitlab.2iopenservice.com/opentalent/maestro/-/blob/master/src/Entity/JobPosting/JobPosting.php?ref_type=heads
  */
 export default class JobPosting extends ApiModel {
-  static entity = 'job-postings'
+  static entity = 'job_postings'
 
   @Uid()
   declare id: number

+ 17 - 0
models/Maestro/Tag.ts

@@ -0,0 +1,17 @@
+import ApiModel from "~/models/ApiModel";
+import {Uid, Str, Bool, Attr} from "pinia-orm/dist/decorators";
+
+/**
+ * Maestro Model : News
+ *
+ * @see https://gitlab.2iopenservice.com/opentalent/maestro/-/blob/master/src/Entity/News/News.php?ref_type=heads
+ */
+export default class Tag extends ApiModel {
+  static entity = 'tags'
+
+  @Uid()
+  declare id: number | string
+
+  @Str(null)
+  declare name: string | null
+}

+ 7 - 0
nuxt.config.ts

@@ -31,11 +31,13 @@ export default defineNuxtConfig({
     // Private config that is only available on the server
     env: "",
     apiBaseUrl: "",
+    agendaBaseUrl: "",
     hCaptchaSiteKey: "35360874-ebb1-4748-86e3-9b156d5bfc53",
     // Config within public will be also exposed to the client
     public: {
       env: "",
       apiBaseUrl: "",
+      agendaBaseUrl: "",
       hCaptchaSiteKey: "35360874-ebb1-4748-86e3-9b156d5bfc53",
     },
   },
@@ -98,6 +100,11 @@ export default defineNuxtConfig({
     "@nuxt/devtools",
     'nuxt3-leaflet'
   ],
+  router: {
+    options: {
+      scrollBehaviorType: 'smooth'
+    }
+  },
   webfontloader: {
     google: {
       families: ["Barlow:300,400,500,700&display=swap"],

+ 18 - 40
pages/CGV.vue

@@ -1,15 +1,15 @@
 <template>
-  <LayoutNavigation />
-  <LayoutUITitlePage
-    title="Conditions générales de vente"
-    style="margin-top: 5rem"
-  />
+  <LayoutUITitlePage>
+    Conditions générales de vente
+  </LayoutUITitlePage>
+
   <CommonBanner
-    :imageSrc="'/images/Bannieres_Mentions_legales-CGV-Cookies.png'"
-    imageAlt="'line'"
+    imageSrc="/images/Bannieres_Mentions_legales-CGV-Cookies.png"
+    imageAlt="banner"
   />
+
   <v-container>
-    <v-row>
+    <v-row class="center-90">
       <v-col cols="12">
         <h2>DÉFINITIONS</h2>
 
@@ -137,7 +137,7 @@
           soit, tenue pour responsable de difficultés liées, en tout ou partie :
         </p>
         <br />
-        <ul class="liste">
+        <ul>
           <li>
             1&rpar; à la force majeure, et notamment, outre les événements
             habituellement retenus par la jurisprudence des juridictions
@@ -313,7 +313,7 @@
 
           Le CLIENT s’engage par ailleurs à ne pas :
         </p>
-        <ul class="liste">
+        <ul>
           <li>
             1&rpar; effectuer ou faire effectuer de copie, de modification, de
             traduction, de transcription, d’adaptation, d’arrangement ou de
@@ -411,7 +411,7 @@
           confidentialité et de sécurité :
         </p>
         <br />
-        <ul class="liste">
+        <ul>
           <li>1&rpar; indépendance ;</li>
           <li>
             2&rpar; établissement et fourniture à première demande de la
@@ -764,34 +764,14 @@
   <LayoutFooter />
 </template>
 
-<style scoped>
-:deep().logiciel {
-  font-family: "Barlow";
-  font-style: normal;
-  font-size: 3rem;
-  line-height: 85px;
-  text-align: center;
-  color: #000000;
-  width: 100%;
-}
-
-:deep().text-left,
-:deep().text-right,
-:deep().description-square,
-:deep().black-square,
-:deep().blue-square {
-  display: none;
-}
-:deep().text-right {
-  display: none;
-}
+<style scoped lang="scss">
 h2 {
   font-size: 1.5rem !important;
   font-weight: 500;
   line-height: 2.5rem;
   letter-spacing: 0.1rem;
   text-transform: uppercase;
-  color: #000000;
+  color: var(--on-neutral-color);
   margin-top: 1rem;
 }
 
@@ -800,20 +780,18 @@ h3 {
   font-size: 1rem !important;
 }
 
-h2,
-h3,
-p,
-ul,
-li {
+.v-container {
   text-align: justify;
 }
-.liste {
+
+ul {
   list-style-type: none;
   padding-left: 0;
 }
 
 a {
-  color: blue;
   text-decoration: none !important;
 }
 </style>
+<script setup lang="ts">
+</script>

+ 0 - 4
pages/actualites/[id].vue

@@ -1,9 +1,5 @@
 <template>
-  <LayoutNavigation />
-
   <NewsDetails />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>

+ 0 - 4
pages/actualites/index.vue

@@ -1,9 +1,5 @@
 <template>
-  <LayoutNavigation />
-
   <NewsList />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>

+ 0 - 4
pages/formations.vue

@@ -1,6 +1,4 @@
 <template>
-  <LayoutNavigation />
-
   <LayoutUITitlePage>
     Formation
     <template #subtitle>
@@ -38,8 +36,6 @@
   <LayoutFAQ />
 
   <LayoutFooterSolutions />
-
-  <LayoutFooter />
 </template>
 
 <script setup lang="ts">

+ 0 - 3
pages/index.vue

@@ -1,5 +1,4 @@
 <template>
-  <LayoutNavigation />
 
   <CommonActionMenu />
 
@@ -25,8 +24,6 @@
   <HomeHelp />
 
   <LayoutFooterPrefooter v-if="lgAndUp" />
-
-  <LayoutFooter />
 </template>
 
 <script setup>

+ 13 - 29
pages/mentions-legales.vue

@@ -1,12 +1,15 @@
 <template>
-  <LayoutNavigation />
-  <LayoutUITitlePage title="Mentions légales" style="margin-top: 5rem" />
+  <LayoutUITitlePage>
+    Mentions légales
+  </LayoutUITitlePage>
+
   <CommonBanner
-    :imageSrc="'/images/Bannieres_Mentions_legales-CGV-Cookies.png'"
-    imageAlt="'line'"
+    imageSrc="/images/Bannieres_Mentions_legales-CGV-Cookies.png"
+    imageAlt="banner"
   />
+
   <v-container>
-    <v-row>
+    <v-row class="center-90">
       <v-col cols="12">
         <p>
           En vertu des dispositions légales en vigueur, nous vous informons des
@@ -139,31 +142,10 @@
       </v-col>
     </v-row>
   </v-container>
-  <LayoutFooter />
 </template>
 
-<style scoped>
-:deep().logiciel {
-  font-family: "Barlow";
-  font-style: normal;
-  font-size: 3rem;
-  line-height: 85px;
-  text-align: center;
-  color: #000000;
-  width: 100%;
-}
-
-:deep().text-left,
-:deep().text-right,
-:deep().description-square,
-:deep().black-square,
-:deep().blue-square {
-  display: none;
-}
-:deep().text-right {
-  display: none;
-}
-* {
+<style scoped lang="scss">
+.v-container {
   text-align: justify;
 }
 
@@ -173,7 +155,9 @@ h3 {
   line-height: 2.5rem;
   letter-spacing: 0.1rem;
   text-transform: uppercase;
-  color: #000000;
+  color: var(--on-neutral-color);
   margin-top: 1rem;
 }
 </style>
+<script setup lang="ts">
+</script>

+ 0 - 6
pages/nous-contacter.vue

@@ -1,6 +1,4 @@
 <template>
-  <LayoutNavigation />
-
   <ContactBanner />
 
   <ContactForm />
@@ -8,8 +6,6 @@
   <ContactMap class="contact-map" />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>
 
 <style scoped lang="scss">
@@ -17,5 +13,3 @@
   margin-top: 48px;
 }
 </style>
-<script setup lang="ts">
-</script>

+ 0 - 4
pages/nous-rejoindre/[id].vue

@@ -1,6 +1,4 @@
 <template>
-  <LayoutNavigation />
-
   <CommonBanner
     imageSrc="/images/actu/pub.png"
     imageAlt="banner"
@@ -9,6 +7,4 @@
   <JoinUsMissionDetail />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>

+ 0 - 4
pages/nous-rejoindre/index.vue

@@ -1,6 +1,4 @@
 <template>
-  <LayoutNavigation />
-
   <LayoutUITitlePage>
     Nous rejoindre
   </LayoutUITitlePage>
@@ -22,8 +20,6 @@
   <JoinUsMissions />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>
 
 <style scoped lang="scss">

+ 3 - 6
pages/opentalent_artist.vue

@@ -1,7 +1,5 @@
 <template>
   <div class="theme-artist" >
-    <LayoutNavigation />
-
     <CommonActionMenu :actions="stickyMenuActions" />
 
     <LogicielsTitle>
@@ -15,6 +13,7 @@
       image-alt="banner artist"
       square-text="Orchestres, chorales, compagnies de danse, de cirque et de théâtre"
       logo-src="/images/logo/logiciels/Artist-noir.png"
+      :logo-alt-theme="true"
     />
 
     <CommonMenuScroll :menus="menus" class="mb-6" />
@@ -38,8 +37,6 @@
     <LayoutFAQ />
 
     <LayoutFooterPrefooter />
-
-    <LayoutFooter />
   </div>
 </template>
 
@@ -80,8 +77,8 @@ const stickyMenuActions: Array<ActionMenuItem> = [
 
 <style scoped lang="scss">
 .theme-artist {
-  --primary-color: var(--on-primary-color);
-  --on-primary-color: var(--primary-color);
+  --primary-color: #ffffff;
+  --on-primary-color: #0e2d32;
   --primary-color-alt: var(--on-secondary-color);
   --on-primary-color-alt: var(--artist-color);
   --secondary-color: var(--artist-color-light);

+ 4 - 10
pages/opentalent_manager.vue

@@ -1,7 +1,5 @@
 <template>
   <div class="theme-manager">
-    <LayoutNavigation />
-
     <CommonBannerTitle
       leftText="School"
       title="Opentalent Manager"
@@ -19,10 +17,8 @@
       imageAlt="line"
       squareText="Fédérations, confédérations et collectivités"
       logoSrc="/images/logo/logiciels/Manager-noir.png"
-      squareColor="#d8050b"
-      blueSquareColor="#FFF"
-      textColor="#FFF"
-    /> <!-- TODO: theming -->
+      :logo-alt-theme="true"
+    />
 
     <CommonMenuScroll :menus="menus" class="mb-6" />
 
@@ -45,8 +41,6 @@
     <LayoutFAQ />
 
     <LayoutFooterSolutions :show-manager="false" />
-
-    <LayoutFooter />
   </div>
 </template>
 
@@ -93,8 +87,8 @@ const stickyMenuActions: Array<ActionMenuItem> = [
 
 <style scoped>
 .theme-manager {
-  --primary-color: var(--on-primary-color);
-  --on-primary-color: var(--primary-color);
+  --primary-color: #ffffff;
+  --on-primary-color: #0e2d32;
   --on-primary-color-alt: var(--manager-color);
   --secondary-color: var(--manager-color-light);
 

+ 2 - 6
pages/opentalent_school.vue

@@ -1,7 +1,5 @@
 <template>
   <div class="theme-school">
-    <LayoutNavigation />
-
     <LogicielsTitle>
       <template #left-text>Artist</template>
       Opentalent School
@@ -39,8 +37,6 @@
     <LayoutFAQ  />
 
     <LayoutFooterSolutions :show-school="false" />
-
-    <LayoutFooter />
   </div>
 </template>
 
@@ -95,8 +91,8 @@ const stickyMenuActions: Array<ActionMenuItem> = [
 
 <style scoped lang="scss">
 .theme-school {
-  --primary-color: var(--on-primary-color);
-  --on-primary-color: var(--primary-color);
+  --primary-color: #ffffff;
+  --on-primary-color: #0e2d32;
   --primary-color-alt: var(--on-secondary-color);
   --on-primary-color-alt: var(--school-color);
   --secondary-color: var(--school-color-light);

+ 0 - 0
pages/poc.vue


+ 19 - 44
pages/politique-de-confidentialite-et-protection-des-donnees-personnelles.vue

@@ -1,15 +1,15 @@
 <template>
-  <LayoutNavigation />
-  <LayoutUITitlePage
-    title="Conditions générales de vente"
-    style="margin-top: 5rem"
-  />
+  <LayoutUITitlePage>
+    POLITIQUE DE CONFIDENTIALITÉ
+  </LayoutUITitlePage>
+
   <CommonBanner
-    :imageSrc="'/images/Bannieres_Mentions_legales-CGV-Cookies.png'"
-    imageAlt="'line'"
+    imageSrc="/images/Bannieres_Mentions_legales-CGV-Cookies.png"
+    imageAlt="banner"
   />
+
   <v-container>
-    <v-row>
+    <v-row class="center-90">
       <v-col cols="12">
         <h2>
           POLITIQUE DE CONFIDENTIALITÉ ET PROTECTION DES DONNÉES PERSONNELLES
@@ -139,7 +139,7 @@
           CLIENTS, à :
         </p>
         <div class="ml-12 mb-4">
-          <ul class="liste">
+          <ul>
             <li>
               1.traiter les données
               <strong> uniquement pour la ou les seule(s) finalité(s)</strong>
@@ -498,7 +498,7 @@
         </h3>
         <p>Le responsable de traitement s’engage à :</p>
         <div class="ml-12 mb-4">
-          <ul class="liste">
+          <ul>
             <li>
               1.fournir à 2IOPENSERVICE les données visées au II des présentes
               clauses
@@ -591,57 +591,32 @@
       </v-col>
     </v-row>
   </v-container>
-  <LayoutFooter />
 </template>
-<style scoped>
-:deep().logiciel {
-  font-family: "Barlow";
-  font-style: normal;
-  font-size: 3rem;
-  line-height: 85px;
-  text-align: center;
-  color: #000000;
-  width: 100%;
+<style scoped lang="scss">
+h2, h3 {
+  text-transform: uppercase;
+  font-weight: 500;
+  color: var(--on-neutral-color);
+  margin-top: 1rem;
 }
 
-:deep().text-left,
-:deep().text-right,
-:deep().description-square,
-:deep().black-square,
-:deep().blue-square {
-  display: none;
-}
-:deep().text-right {
-  display: none;
-}
 h2 {
   font-size: 1.5rem !important;
-  font-weight: 500;
   line-height: 2.5rem;
   letter-spacing: 0.1rem;
-  text-transform: uppercase;
-  color: #000000;
   margin-bottom: 1rem;
-  margin-top: 1rem;
 }
 
 h3 {
   font-size: 1rem !important;
-  font-weight: 500;
   line-height: 2rem;
-  text-transform: uppercase;
-  color: #000000;
-  margin-top: 1rem;
 }
 
-h2,
-h3,
-p,
-ul,
-li {
+.v-container {
   text-align: justify;
 }
-.liste {
+
+ul {
   list-style-type: none;
   padding-left: 0;
 }

+ 1 - 5
pages/qui-sommes-nous.vue

@@ -3,8 +3,6 @@
 -->
 
 <template>
-  <LayoutNavigation />
-
   <LayoutUITitlePage class="mb-12">
     Qui sommes-nous ?
   </LayoutUITitlePage>
@@ -24,7 +22,7 @@
 
   <AboutLogiciels />
 
-  <CommonAgenda />
+  <HomeEventAgenda class="mb-6"/>
 
   <AboutChronologie v-if="lgAndUp"/>
 
@@ -33,8 +31,6 @@
   <AboutFAQ />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>
 
 <script setup lang="ts">

+ 0 - 4
pages/webinaires.vue

@@ -1,6 +1,4 @@
 <template>
-  <LayoutNavigation />
-
   <CommonActionMenu />
 
   <LayoutUITitlePage>
@@ -18,8 +16,6 @@
   <WebinaireFAQ />
 
   <LayoutFooterPrefooter />
-
-  <LayoutFooter />
 </template>
 
 <script setup>

+ 0 - 14
plugins/router.ts

@@ -1,14 +0,0 @@
-/**
- * Redéfinit le comportement du router quand on navigue vers une ancre
- */
-export default defineNuxtPlugin(({ $router }) => {
-  $router.options.scrollBehavior = (to: { hash: any}, from: any, savedPosition: any) => {
-    if (to.hash) {
-      return {
-        el: to.hash,
-        top: 30,
-        behavior: 'smooth'
-      }
-    }
-  }
-})

+ 34 - 0
services/utils/FileUtils.ts

@@ -0,0 +1,34 @@
+/**
+ * Manipulation des images
+ */
+class FileUtils {
+
+  /**
+   * Returns a blob with the given data and the image filetype
+   *
+   * @param data
+   * @param filetype
+   */
+  public static newBlob(data: string, filetype: string = 'image/jpeg'): Blob {
+    return new Blob([data as BlobPart], {type: filetype});
+  }
+
+  /**
+   * Transforme un Blob en Base64
+   * @param {Blob} blob
+   */
+  public static async blobToBase64(blob: Blob): Promise<string> {
+    return new Promise((resolve, _) => {
+      const reader = new FileReader();
+      // @ts-ignore
+      reader.onloadend = () => {
+        // @ts-ignore
+        let base64Data: string = reader.result;
+        base64Data = base64Data.substring(base64Data.indexOf(',') + 1);
+        resolve(base64Data);
+      };
+      reader.readAsDataURL(blob);
+    });
+  }
+}
+export default FileUtils

+ 2 - 1
services/utils/dateUtils.ts

@@ -1,4 +1,5 @@
-import { format } from 'date-fns';
+import { format } from "date-fns";
+import type { Locale } from "date-fns";
 import ArrayUtils from "~/services/utils/arrayUtils";
 import { enUS, fr } from 'date-fns/locale'
 

+ 8 - 0
stores/layoutStore.ts

@@ -2,6 +2,12 @@ import type { Ref } from "@vue/reactivity";
 
 
 export const useLayoutStore = defineStore('layout', () => {
+  const isHeaderVisible: Ref<boolean> = ref(false)
+
+  const setIsHeaderVisible = (value: boolean) => {
+    isHeaderVisible.value = value
+  }
+
   const isFooterVisible: Ref<boolean> = ref(false)
 
   const setIsFooterVisible = (value: boolean) => {
@@ -15,6 +21,8 @@ export const useLayoutStore = defineStore('layout', () => {
   }
 
   return {
+    isHeaderVisible,
+    setIsHeaderVisible,
     isFooterVisible,
     setIsFooterVisible,
     isAnchoredSectionOnScreen,

+ 24 - 20
yarn.lock

@@ -2118,7 +2118,7 @@
     "@vue/compiler-dom" "3.4.19"
     "@vue/shared" "3.4.19"
 
-"@vue/devtools-api@^6.5.0":
+"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1":
   version "6.6.1"
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
   integrity sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==
@@ -2619,17 +2619,16 @@ bundle-name@^4.1.0:
     run-applescript "^7.0.0"
 
 c12@^1.5.1, c12@^1.7.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/c12/-/c12-1.8.0.tgz#6be4c9540782f624cfb41820460ce36534f0ee35"
-  integrity sha512-93U6RndoaAwFQPBcS9F/6lwtgBfrWh4695sQ/ChILkbj0C7zOZVptOU3Sxp0I/9xvfW/lzBWD90AXDQz4muSkA==
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/c12/-/c12-1.9.0.tgz#a98b3d16b5010667983df70794a332d6b9865ec3"
+  integrity sha512-7KTCZXdIbOA2hLRQ+1KzJ15Qp9Wn58one74dkihMVp2H6EzKTa3OYBy0BSfS1CCcmxYyqeX8L02m40zjQ+dstg==
   dependencies:
     chokidar "^3.5.3"
+    confbox "^0.1.3"
     defu "^6.1.4"
     dotenv "^16.3.2"
     giget "^1.2.1"
     jiti "^1.21.0"
-    json5 "^2.2.3"
-    jsonc-parser "^3.2.1"
     mlly "^1.5.0"
     ohash "^1.1.3"
     pathe "^1.1.2"
@@ -2710,9 +2709,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001578, caniuse-lite@^1.0.30001587:
-  version "1.0.30001588"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz#07f16b65a7f95dba82377096923947fb25bce6e3"
-  integrity sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==
+  version "1.0.30001589"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz#7ad6dba4c9bf6561aec8291976402339dc157dfb"
+  integrity sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==
 
 chalk@^2.4.2:
   version "2.4.2"
@@ -2903,6 +2902,11 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
+confbox@^0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.3.tgz#121eaeb7ec916215afe351449895290a2a270434"
+  integrity sha512-eH3ZxAihl1PhKfpr4VfEN6/vUd87fmgb6JkldHgg/YR6aEBhW63qUDgzP2Y6WM0UumdsYp5H3kibalXAdHfbgg==
+
 consola@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f"
@@ -3310,9 +3314,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.4.668:
-  version "1.4.677"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.677.tgz#49ee77713516740bdde32ac2d1443c444f0dafe7"
-  integrity sha512-erDa3CaDzwJOpyvfKhOiJjBVNnMM0qxHq47RheVVwsSQrgBA9ZSGV9kdaOfZDPXcHzhG7lBxhj6A7KvfLJBd6Q==
+  version "1.4.679"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.679.tgz#480f497874ce2be162c0ac271eec91918bf96247"
+  integrity sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==
 
 emoji-regex@^8.0.0:
   version "8.0.0"
@@ -5083,7 +5087,7 @@ jsonc-eslint-parser@^2.3.0:
     espree "^9.0.0"
     semver "^7.3.5"
 
-jsonc-parser@^3.2.0, jsonc-parser@^3.2.1:
+jsonc-parser@^3.2.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a"
   integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==
@@ -8099,11 +8103,11 @@ vue-i18n@^9.9.0:
     "@vue/devtools-api" "^6.5.0"
 
 vue-router@^4.2.5:
-  version "4.2.5"
-  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a"
-  integrity sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.0.tgz#d5913f27bf68a0a178ee798c3c88be471811a235"
+  integrity sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==
   dependencies:
-    "@vue/devtools-api" "^6.5.0"
+    "@vue/devtools-api" "^6.5.1"
 
 vue3-carousel@^0.3.1:
   version "0.3.1"
@@ -8122,9 +8126,9 @@ vue@^3.2.19, vue@^3.2.25, vue@^3.4.19:
     "@vue/shared" "3.4.19"
 
 vuetify@^3.5.3:
-  version "3.5.4"
-  resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.5.4.tgz#f919c5194995a123815c277a95812bc230e33464"
-  integrity sha512-fHgfWMI7+z/UtbVPOezX+O1MNBOOMBW9HnKejcBIyQQ7jFRnTHbDQmbINf25FK0wrg/zkjfzyOmWWREKW39eXg==
+  version "3.5.5"
+  resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.5.5.tgz#e625505d8b18db4f35b2619450e689893d497c70"
+  integrity sha512-WENee4TkmWmwMEtmAHaljQZjR2AdmK1zetzH2rpEhlvkiTDEuIB0yKLGBv+vshH0XBRS84cf7uSjfceOvAhhSQ==
 
 w3c-xmlserializer@^5.0.0:
   version "5.0.0"