Browse Source

Merge branch 'release/1.0'

Olivier Massot 1 year ago
parent
commit
8abaf0cfb6
100 changed files with 2344 additions and 720 deletions
  1. 9 0
      .eslintrc.cjs
  2. 1 0
      .gitignore
  3. 9 2
      README.md
  4. 24 2
      app.vue
  5. 12 2
      assets/style/main.scss
  6. 2 0
      assets/style/theme.scss
  7. 10 2
      components/About/Chronologie.client.vue
  8. 3 3
      components/About/Equipe.vue
  9. 1 1
      components/About/FAQ.vue
  10. 10 5
      components/About/Logiciels.vue
  11. 25 24
      components/About/Presentation.vue
  12. 7 2
      components/About/Valeurs.vue
  13. 56 34
      components/Common/ActionMenu.vue
  14. 10 2
      components/Common/Agenda.vue
  15. 1 1
      components/Common/AgendaLink.vue
  16. 1 2
      components/Common/Banner.vue
  17. 17 3
      components/Common/Carousel/Clients.client.vue
  18. 14 6
      components/Common/Carousel/Fonctionnalite.client.vue
  19. 13 8
      components/Common/Contact.vue
  20. 598 0
      components/Common/CookiesConsent.vue
  21. 558 0
      components/Common/Error/NotFound.vue
  22. 9 9
      components/Common/MenuScroll.vue
  23. 1 1
      components/Common/Presentation.vue
  24. 10 2
      components/Common/ReviewSection.client.vue
  25. 16 6
      components/Common/Table/Comparatif.vue
  26. 8 6
      components/Contact/Form.vue
  27. 5 1
      components/Contact/Map.client.vue
  28. 106 78
      components/Formation/Catalogue.vue
  29. 3 3
      components/Formation/Certification.vue
  30. 22 10
      components/Formation/OPCA.vue
  31. 17 14
      components/Formation/Participation.vue
  32. 4 4
      components/Formation/Presentation.vue
  33. 1 1
      components/Home/Besoin.vue
  34. 8 8
      components/Home/Caroussel.vue
  35. 2 4
      components/Home/EventAgenda.vue
  36. 32 28
      components/Home/Promotion.vue
  37. 12 4
      components/Home/Reviews.client.vue
  38. 6 6
      components/Home/Solution.vue
  39. 68 0
      components/JoinUs/Dialog.vue
  40. 43 22
      components/JoinUs/Form.vue
  41. 13 6
      components/JoinUs/MissionDetail.vue
  42. 8 46
      components/JoinUs/Missions.client.vue
  43. 12 2
      components/Layout/AnchoredSection.vue
  44. 30 1
      components/Layout/Captcha.vue
  45. 6 1
      components/Layout/FAQ.vue
  46. 52 42
      components/Layout/Footer/Footer.vue
  47. 9 9
      components/Layout/Footer/Prefooter.vue
  48. 5 10
      components/Layout/Navigation.vue
  49. 2 0
      components/Layout/Navigation/Lg.vue
  50. 14 13
      components/Layout/Navigation/Md.vue
  51. 3 1
      components/Layout/Navigation/Topbar.vue
  52. 3 3
      components/Layout/UI/TitlePage.vue
  53. 4 4
      components/Logiciels/Artist/Abonnement.vue
  54. 1 3
      components/Logiciels/Artist/Abonnement/ToSubscribe.vue
  55. 18 18
      components/Logiciels/Artist/Fonctionnalites.vue
  56. 8 11
      components/Logiciels/Artist/Formations.vue
  57. 1 1
      components/Logiciels/Artist/SomeNumbers.vue
  58. 5 5
      components/Logiciels/Manager/Avantages.vue
  59. 1 1
      components/Logiciels/Manager/Fonctionnalites.vue
  60. 1 1
      components/Logiciels/Manager/Presentation.vue
  61. 1 1
      components/Logiciels/Manager/SomeNumbers.vue
  62. 43 16
      components/Logiciels/School/Comparatif.vue
  63. 9 9
      components/Logiciels/School/Fonctionnalites.vue
  64. 3 3
      components/Logiciels/School/Reviews.vue
  65. 4 5
      components/Logiciels/School/SomeNumbers.vue
  66. 25 0
      components/News/Details.vue
  67. 0 0
      components/News/List.client.vue
  68. 133 116
      components/Webinaire/Catalogue.vue
  69. 11 8
      components/Webinaire/FAQ.vue
  70. 15 1
      composables/useClientDevice.ts
  71. 4 1
      env/.env.ci
  72. 3 0
      env/.env.docker
  73. 7 2
      env/.env.prod
  74. 5 0
      env/.env.test
  75. 5 0
      env/.env.test1
  76. 5 0
      env/.env.test2
  77. 5 0
      env/.env.test3
  78. 5 0
      env/.env.test4
  79. 5 0
      env/.env.test5
  80. 5 0
      env/.env.test6
  81. 5 0
      env/.env.test7
  82. 5 0
      env/.env.test8
  83. 5 0
      env/.env.test9
  84. 0 20
      env/local.portail_v2.opentalent.fr.crt
  85. 0 28
      env/local.portail_v2.opentalent.fr.key
  86. 1 1
      env/setupEnv.mjs
  87. 29 0
      error.vue
  88. 8 0
      lang/fr.json
  89. 4 1
      models/Maestro/JobApplication.ts
  90. 1 1
      models/Maestro/JobPosting.ts
  91. 1 1
      models/Maestro/News.ts
  92. 25 4
      nuxt.config.ts
  93. 11 12
      package.json
  94. 0 0
      pages/actualites/[id].client.vue
  95. 1 1
      pages/cgv.vue
  96. 1 1
      pages/formations.vue
  97. 1 1
      pages/mentions-legales.vue
  98. 1 1
      pages/nous-contacter.vue
  99. 1 1
      pages/nous-rejoindre/[id].client.vue
  100. 1 1
      pages/nous-rejoindre/index.vue

+ 9 - 0
.eslintrc.cjs

@@ -47,5 +47,14 @@ module.exports = {
     useLayoutStore: 'readonly',
     useClientDevice: 'readonly',
     useSeoMeta: 'readonly',
+    useServerSeoMeta: 'readonly',
+    onNuxtReady: 'readonly',
+    useLocaleHead: 'readonly',
+    useHead: 'readonly',
+    useError: 'readonly',
+    Ref: 'readonly',
+    watch: 'readonly',
+    useGtag: 'readonly',
+    defineEmits: 'readonly'
   },
 }

+ 1 - 0
.gitignore

@@ -17,3 +17,4 @@ dist
 !.yarn/releases
 !.yarn/sdks
 !.yarn/versions
+/public/.htaccess

+ 9 - 2
README.md

@@ -1,7 +1,7 @@
 
-[![pipeline status](http://gitlab.2iopenservice.com/opentalent/portail_v2/badges/master/pipeline.svg)](http://gitlab.2iopenservice.com/opentalent/portail_v2/-/commits/master) 
+[![pipeline status](http://gitlab.2iopenservice.com/opentalent/site_logiciels/badges/master/pipeline.svg)](http://gitlab.2iopenservice.com/opentalent/site_logiciels/-/commits/master) 
 
-[![Latest Release](http://gitlab.2iopenservice.com/opentalent/portail_v2/-/badges/release.svg)](http://gitlab.2iopenservice.com/opentalent/portail_v2/-/releases) 
+[![Latest Release](http://gitlab.2iopenservice.com/opentalent/site_logiciels/-/badges/release.svg)](http://gitlab.2iopenservice.com/opentalent/site_logiciels/-/releases) 
 
 # Site Logiciels
 
@@ -28,3 +28,10 @@ Développé en nuxt3.
 
     yarn generate
 
+### Mise en prod
+    cd /var/opentalent/git/site_logiciels
+    git pull
+    nvm exec yarn generate
+    nvm exec yarn build
+    sudo supervisorctl restart site_logiciels:site_logiciels_00
+

+ 24 - 2
app.vue

@@ -1,5 +1,7 @@
 <template>
-  <div>
+  <Html :lang="i18nHead.htmlAttrs.lang" :dir="i18nHead.htmlAttrs.dir">
+    <CommonCookiesConsent />
+
     <div id="top" />
 
     <LayoutNavigation />
@@ -7,11 +9,31 @@
     <nuxt-page />
 
     <LayoutFooter />
-  </div>
+  </Html>
 </template>
 
 <script setup lang="ts">
+import UrlUtils from '~/services/utils/urlUtils'
+
 const layoutStore = useLayoutStore()
 
 layoutStore.resetAnchoredSections()
+
+const i18nHead = useLocaleHead({
+  addDirAttribute: true,
+  identifierAttribute: 'id',
+  addSeoAttributes: true,
+})
+
+const route = useRoute()
+const runtimeConfig = useRuntimeConfig()
+
+useHead(() => ({
+  link: [
+    {
+      rel: 'canonical',
+      href: UrlUtils.join(runtimeConfig.public.siteUrl as string, route.path),
+    },
+  ],
+}))
 </script>

+ 12 - 2
assets/style/main.scss

@@ -1,5 +1,3 @@
-@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@500;700&display=swap');
-
 body {
   font-family: "Barlow", serif;
   font-style: normal;
@@ -38,3 +36,15 @@ body {
 h1, h2, h3, h4, h5, li, .v-btn {
   text-align: start;
 }
+
+.lg-and-up {
+  @media (max-width: 1240px) {
+    display: none !important;
+  }
+}
+
+.md-and-down {
+  @media (min-width: 1240px) {
+    display: none !important;
+  }
+}

+ 2 - 0
assets/style/theme.scss

@@ -37,6 +37,8 @@ body {
   --artist-color-light: #fef3ce;
   --school-color-light: #a5d4e5;
   --manager-color-light: #f7cdce;
+
+  --warning-color: #D8050B;
 }
 
 body {

+ 10 - 2
components/About/Chronologie.vue → components/About/Chronologie.client.vue

@@ -11,8 +11,16 @@
 
           <v-col cols="12" md="6">
             <div class="carousel-controls">
-              <v-btn icon="fas fa-chevron-left" @click="goPrevious" />
-              <v-btn icon="fas fa-chevron-right" @click="goNext" />
+              <v-btn
+                icon="fas fa-chevron-left"
+                aria-label="Précédent"
+                @click="goPrevious"
+              />
+              <v-btn
+                icon="fas fa-chevron-right"
+                aria-label="Suivant"
+                @click="goNext"
+              />
             </div>
           </v-col>
         </v-row>

+ 3 - 3
components/About/Equipe.vue

@@ -9,7 +9,7 @@
         <LayoutUITitle> Une équipe spécialisée et passionnée </LayoutUITitle>
 
         <span class="details ml-4 mt-6 mb-12">
-          Chez Opentalent, on recherche des compétences mais surtout des hommes
+          Chez Opentalent, on recherche des compétences, mais surtout des hommes
           et des femmes qui souhaitent s'engager dans un projet porteur de sens.
         </span>
       </v-row>
@@ -77,7 +77,7 @@ const associates: Array<SocietyMember> = [
   },
   {
     name: 'Michel',
-    position: 'Co-fondateur / Product Owner',
+    position: 'Co-fondateur / CTO',
     photo:
       '/images/pages/qui-sommes-nous/equipe/Michel_PERNET-SOLLIET-Co-fondateur_et_Product_Owner.png',
     alt: 'Avatar d’un homme avec les cheveux bruns mi-long portant un pull jaune et un pantalon noir',
@@ -137,7 +137,7 @@ const employees: Array<SocietyMember> = [
     position: 'Développeuse',
     photo:
       '/images/pages/qui-sommes-nous/equipe/Maha_BOUCHIBA-Developpeuse.png',
-    alt: 'Avatar d’une femme portant un turban sur les cheveux, haut beige et blanc et un jean bleu foncé',
+    alt: 'Avatar d’une femme portant un voile sur les cheveux, haut beige et blanc et un jean bleu foncé',
   },
 ]
 </script>

+ 1 - 1
components/About/FAQ.vue

@@ -13,7 +13,7 @@
         <v-col cols="12" md="6">
           <div class="d-flex flex-column">
             <h4>
-              Chez Opentalent, nous avons à coeur de répondre à vos
+              Chez Opentalent, nous avons à c&oelig;ur de répondre à vos
               interrogations et de vous apporter la solution faite pour vous.
             </h4>
 

+ 10 - 5
components/About/Logiciels.vue

@@ -40,7 +40,7 @@
               <footer>
                 <v-img :src="item.logoUrl" :alt="item.logoAlt" class="logo" />
 
-                <v-btn class="plus-button">
+                <v-btn aria-label="Voir plus" class="plus-button">
                   <v-icon>fas fa-plus</v-icon>
                 </v-btn>
               </footer>
@@ -81,7 +81,7 @@ const items: Array<{
     logoAlt:
       'Logo Opentalent Artist - logiciel de gestion et de communication pour les orchestres, les chorales, les compagnies artistiques et troupes',
     class: 'artist',
-    link: '/opentalent_artist',
+    link: '/opentalent-artist',
   },
   {
     imageUrl:
@@ -91,7 +91,7 @@ const items: Array<{
     logoAlt:
       'Logo Opentalent School - logiciel de gestion et de communication pour les établissements d’enseignement artistique',
     class: 'school',
-    link: '/opentalent_school',
+    link: '/opentalent-school',
   },
   {
     imageUrl:
@@ -101,7 +101,7 @@ const items: Array<{
     logoAlt:
       'Logo Opentalent Manager - logiciel de gestion et de communication pour les fédérations, les confédérations et les collectivités',
     class: 'manager',
-    link: '/opentalent_manager',
+    link: '/opentalent-manager',
   },
 ]
 </script>
@@ -180,6 +180,7 @@ li:before {
     border-top-left-radius: 10% !important;
     margin-right: -10px;
     margin-bottom: -10px;
+    z-index: 2;
 
     .v-icon {
       color: var(--on-primary-color);
@@ -188,8 +189,12 @@ li:before {
   }
 }
 
+.plus-button:hover {
+  filter: brightness(90%);
+}
+
 .container-image::after {
-  content: "";
+  content: '';
   position: absolute;
   top: 0;
   bottom: 0;

+ 25 - 24
components/About/Presentation.vue

@@ -31,35 +31,36 @@
 
           <p class="mb-8">
             En 2005, Guillaume CORCOBA, musicien depuis toujours et à ce moment
-            là président d'un orchestre d'harmonie, mais également membre du
-            conseil d'administration de sa fédération, réfléchit à un outil pour
-            centraliser les informations de sa structure, mais également au
-            niveau fédéral. Il souhaite simplifier la gestion des structures
-            culturelles et en faire la promotion, car pour lui, le milieu
-            culturel est indispensable. Il est rapidement rejoint par Michel
-            PERNET-SOLLIET, lui aussi musicien, et ils montent ensemble
-            Openassos, qui deviendra quelques années plus tard, Opentalent.
-            Opentalent c'est un ensemble de 3 logiciels spécialement dédiés à la
-            culture et un agenda culturel pour en faire la promotion.
+            là président de l’orchestre d'harmonie de Cluses, mais également
+            membre du conseil d'administration de l'école de musique de Cluses
+            et de la Fédération des musiques du Faucigny, réfléchit à un outil
+            collaboratif permettant de centraliser les informations des
+            structures culturelles. Son objectif est de simplifier la gestion
+            administrative de ces établissements et d’en faire la promotion,
+            convaincu que le secteur culturel est indispensable à la société. Il
+            est rapidement rejoint par Michel PERNET-SOLLIET, lui aussi
+            musicien, et ils montent ensemble Openassos, qui deviendra quelques
+            années plus tard, Opentalent. Opentalent c'est un ensemble de 3
+            logiciels spécialement dédiés à la culture et un agenda culturel
+            pour en faire la promotion.
           </p>
 
           <h3>La Culture au service du développement territorial</h3>
 
           <p>
-            Qui n'a jamais entendu que la culture coûtait trop cher ? On
-            l'entend , ha ça oui on l'a même trop entendu ! Mais la culture
-            c'est avant tout un facteur de lien social incroyable. On se
-            retrouve, on échange, on partage... on vit ensemble. On crée des
-            vrais moments et on développe des groupes de passionnés. On
-            participe à rendre nos collectivités attractives et surtout on les
-            fait vivre, toute l'année, à toutes les saisons. Depuis plusieurs
-            décennies, un grand nombre de territoires s'appuie sur le
-            développement de la culture comme un outil de développement
-            territorial pour faire face à la désindustrialisation, à une
-            croissance démographique ralentie ou encore une image défavorable.
-            Ce modèle de développement par la culture pour pallier un déficit
-            d’attractivité touristique inspire de plus en plus de politiques de
-            développement territorial.
+            Qui n'a jamais entendu dire que la culture coûtait trop cher ? On
+            l'entend, ah ça oui on l'a même trop entendu ! Mais la culture c'est
+            avant tout un facteur de lien social incroyable. On se retrouve, on
+            échange, on partage... on vit ensemble. On crée de vrais moments et
+            on développe des groupes de passionnés. On participe à rendre nos
+            collectivités attractives et surtout on les fait vivre, toute
+            l'année, à toutes les saisons. Depuis plusieurs décennies, un grand
+            nombre de territoires s'appuie sur le développement de la culture
+            pour faire face à la désindustrialisation, à une croissance
+            démographique ralentie ou encore une image défavorable. Ce modèle de
+            développement par la culture pour pallier un déficit d’attractivité
+            touristique inspire de plus en plus de politiques de développement
+            territorial.
           </p>
         </v-col>
       </v-row>

+ 7 - 2
components/About/Valeurs.vue

@@ -71,14 +71,14 @@ const values: Array<Array<SocietyValue>> = [
       alt: 'Icône Écologie',
       title: 'Écologie',
       description:
-        'Proche des entreprises de l’Économie Sociale et Solidaire, Opentalent accorde une grande importance aux démarches liées à l’écologie et au développement durable. Le code des outils est par exemple optimisé pour limiter les ressources nécessaires des serveurs, réduisant ainsi leur empreinte carbone et améliorant le confort des utilisateurs au quotidien.',
+        "Proche des entreprises de l'Économie Sociale et Solidaire, Opentalent s'engage résolument dans une démarche écologique et de développement durable. L'entreprise met tout en œuvre pour réduire son impact environnemental, notamment en recherchant en permanence de nouvelles solutions pour optimiser son impact écologique et contribuer à un avenir plus durable.",
     },
     {
       img: '/images/pages/qui-sommes-nous/valeurs/Open-source.svg',
       alt: 'Icône Open source',
       title: 'Open source',
       description:
-        'Opentalent est une entreprise qui croit profondément aux vertus des logiciels Open Source et qui par son action contribue à leur développement.',
+        'Opentalent est une entreprise qui croit profondément aux vertus des logiciels Open Source et qui, par son action, contribue activement à leur développement. En choisissant le modèle Open Source, nous nous engageons à maintenir un haut niveau de transparence et de contrôle, renforçant ainsi la sécurité et l’efficacité de nos solutions.',
     },
   ],
 ]
@@ -96,6 +96,11 @@ const values: Array<Array<SocietyValue>> = [
   @media (max-width: 1240px) {
     width: 90%;
   }
+
+  @media (max-width: 600px) {
+    height: auto;
+    width: auto;
+  }
 }
 
 .values {

+ 56 - 34
components/Common/ActionMenu.vue

@@ -3,44 +3,48 @@ Menu d'actions rapides (appel, contact, ...), qui reste accroché au bord droit
 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 v-if="lgAndUp && isVisible" class="sticky-menu lateral">
-    <v-row
-      v-for="(action, index) in actionsOrDefault"
-      :key="index"
-      :class="['square', action.color]"
-      @click="() => onActionClick(action)"
-    >
-      <NuxtLink :to="action.url" class="link">
-        <div>
-          <v-icon :class="action.icon" />
-
-          <p class="text-square mt-2">
-            {{ action.text }}
-          </p>
-        </div>
-      </NuxtLink>
-    </v-row>
-  </div>
+  <div v-show="showMenu">
+    <!-- Écrans larges : menu lateral, accroché au bord droit de l'écran -->
+    <div v-if="lgAndUp && isVisible" class="sticky-menu lateral">
+      <v-row
+        v-for="(action, index) in actionsOrDefault"
+        :key="index"
+        :class="['square', action.color]"
+        @click="() => onActionClick(action)"
+      >
+        <NuxtLink class="link">
+          <div>
+            <v-icon :class="action.icon" />
+
+            <p class="text-square mt-2">
+              {{ action.text }}
+            </p>
+          </div>
+        </NuxtLink>
+      </v-row>
+    </div>
+
+    <!-- Petits écrans : menu sous forme de bandeau en pied de page (sauf si le footer du site est visible) -->
+    <div v-else-if="isVisible" class="sticky-menu band">
+      <v-btn
+        v-for="(action, index) in actionsOrDefault"
+        :key="index"
+        :class="[action.color]"
+        @click="() => onActionClick(action)"
+      >
+        <span v-if="mdAndUp">{{ action.text }}</span>
+        <v-icon v-else :aria-label="action.text">{{ action.icon }}</v-icon>
+      </v-btn>
+    </div>
 
-  <!-- Petits écrans : menu sous forme de bandeau en pied de page (sauf si le footer du site est visible) -->
-  <div v-else-if="isVisible" class="sticky-menu band">
     <v-btn
+      v-if="isVisible"
       :to="{ path: '', hash: '#top' }"
-      class="primary"
-      :width="24"
+      aria-label="Revenir en début de page"
+      class="back-to-the-top secondary"
     >
       <v-icon>fas fa-arrow-up</v-icon>
     </v-btn>
-
-    <v-btn
-      v-for="(action, index) in actionsOrDefault"
-      :key="index"
-      :class="[action.color]"
-      @click="() => onActionClick(action)"
-    >
-      {{ action.text }}
-    </v-btn>
   </div>
 </template>
 
@@ -52,7 +56,7 @@ import { useLayoutStore } from '~/stores/layoutStore'
 import { ActionMenuItemType } from '~/types/enum/layout'
 import type { ActionMenuItem } from '~/types/interface'
 
-const { lgAndUp } = useDisplay()
+const { lgAndUp, mdAndUp } = useDisplay()
 const router = useRouter()
 const layoutStore = useLayoutStore()
 const { isMobileDevice } = useClientDevice()
@@ -66,6 +70,12 @@ const isVisible: ComputedRef<boolean> = computed(
     !layoutStore.isFooterVisible
 )
 
+// Attend l'hydratation avant d'afficher
+const showMenu = ref(false)
+onNuxtReady(() => {
+  showMenu.value = true
+})
+
 // Actions par défaut du menu, peut-être surchargé via la propriété `actions`
 const defaultActions: Array<ActionMenuItem> = [
   {
@@ -124,7 +134,9 @@ const onActionClick = (action: ActionMenuItem) => {
       if (!action.url) {
         throw new Error('Missing prop : url')
       }
-      navigateTo(action.url)
+      navigateTo(action.url, {
+        open: { target: action.target ? action.target : '_self' },
+      })
       break
 
     default:
@@ -197,6 +209,16 @@ const onActionClick = (action: ActionMenuItem) => {
   }
 }
 
+.back-to-the-top {
+  position: fixed;
+  right: 12px;
+  bottom: 58px;
+  z-index: 100;
+  height: 48px;
+  width: 48px;
+  border-radius: 32px;
+}
+
 .primary {
   background: var(--action-menu-primary-color);
   color: var(--action-menu-on-primary-color);

+ 10 - 2
components/Common/Agenda.vue

@@ -12,8 +12,16 @@ Section "Agenda des évènements"
         <v-col cols="4">
           <!-- Factoriser les contrôles du carousel dans un component -->
           <div class="carousel-controls">
-            <v-btn icon="fas fa-chevron-left" @click="goPrevious" />
-            <v-btn icon="fas fa-chevron-right" @click="goNext" />
+            <v-btn
+              icon="fas fa-chevron-left"
+              aria-label="Précédent"
+              @click="goPrevious"
+            />
+            <v-btn
+              icon="fas fa-chevron-right"
+              aria-label="Suivant"
+              @click="goNext"
+            />
           </div>
         </v-col>
 

+ 1 - 1
components/Common/AgendaLink.vue

@@ -1,5 +1,5 @@
 <template>
-  <nuxt-link :href="target" target="_blank">
+  <nuxt-link :href="target" target="_self">
     <slot />
   </nuxt-link>
 </template>

+ 1 - 2
components/Common/Banner.vue

@@ -118,7 +118,7 @@ const onIntersect = (isIntersecting: boolean) => {
   margin: 0 auto;
 
   @media (max-width: 600px) {
-    min-height: 0;
+    min-height: 235px;
   }
 }
 
@@ -154,7 +154,6 @@ const onIntersect = (isIntersecting: boolean) => {
 
   img {
     width: 100%;
-    height: 50%;
     margin-top: 2.5rem;
   }
 

+ 17 - 3
components/Common/Carousel/Clients.vue → components/Common/Carousel/Clients.client.vue

@@ -18,6 +18,7 @@
         <v-btn
           v-if="lgAndUp"
           icon="fas fa-chevron-left"
+          aria-label="Précédent"
           @click="goToPrevious"
         />
 
@@ -37,12 +38,25 @@
         </Carousel>
 
         <!-- Fléche de droite -->
-        <v-btn v-if="lgAndUp" icon="fas fa-chevron-right" @click="goToNext" />
+        <v-btn
+          v-if="lgAndUp"
+          icon="fas fa-chevron-right"
+          aria-label="Suivant"
+          @click="goToNext"
+        />
       </v-row>
 
       <v-row v-if="mdAndDown">
-        <v-btn icon="fas fa-chevron-left" @click="goToPrevious" />
-        <v-btn icon="fas fa-chevron-right" @click="goToNext" />
+        <v-btn
+          icon="fas fa-chevron-left"
+          aria-label="Précédent"
+          @click="goToPrevious"
+        />
+        <v-btn
+          icon="fas fa-chevron-right"
+          aria-label="Suivant"
+          @click="goToNext"
+        />
       </v-row>
     </v-container>
   </LayoutContainer>

+ 14 - 6
components/Common/Carousel/Fonctionnalite.vue → components/Common/Carousel/Fonctionnalite.client.vue

@@ -1,7 +1,7 @@
 <template>
   <LayoutContainer>
     <v-row class="center-90">
-      <v-col cols="12" md="6">
+      <v-col cols="12" md="12">
         <LayoutUISubTitle>
           Découvrez toutes les fonctionnalités de notre solution
         </LayoutUISubTitle>
@@ -11,9 +11,17 @@
         </LayoutUITitle>
       </v-col>
 
-      <v-col cols="12" md="6" class="arrows">
-        <v-btn icon="fas fa-chevron-left" @click="previousAction" />
-        <v-btn icon="fas fa-chevron-right" @click="nextAction" />
+      <v-col cols="12" md="12" class="arrows">
+        <v-btn
+          icon="fas fa-chevron-left"
+          aria-label="Précédent"
+          @click="previousAction"
+        />
+        <v-btn
+          icon="fas fa-chevron-right"
+          aria-label="Suivant"
+          @click="nextAction"
+        />
       </v-col>
     </v-row>
 
@@ -24,6 +32,7 @@
           :items-to-show="itemsToShow"
           :items-to-scroll="1"
           :wrap-around="true"
+          snap-align="start"
         >
           <Slide v-for="(card, index) in cards" :key="index">
             <div class="card-container">
@@ -177,11 +186,10 @@ h5 {
 .arrows {
   display: flex;
   align-content: center;
-  justify-content: end;
+  justify-content: center;
 
   @media (max-width: 600px) {
     justify-content: center;
   }
 }
-
 </style>

+ 13 - 8
components/Common/Contact.vue

@@ -20,13 +20,18 @@
           <LayoutUITitle> Vous avez un projet ? </LayoutUITitle>
 
           <div class="px-6">
+            <p class="contact-details mb-3">
+              Vous souhaitez optimiser votre temps et fluidifier votre gestion
+              administrative ? Nous sommes à votre écoute pour vous conseiller
+              et vous proposer une solution parfaitement adaptée à vos besoins
+              !”
+            </p>
             <p class="contact-details">
-              Vous avez une identité, des besoins, des projets... On est là pour
-              vous écouter et vous offrir une offre personnalisée ! Que vous
-              soyez une petite ou une structure plus conséquente, notre offre
-              s'adapte à toutes les tailles : le prix de l’abonnement au
-              logiciel varie en fonction du nombre d'élèves, tout en conservant
-              l'intégralité des fonctionnalités.
+              Que vous soyez une petite ou une structure plus conséquente, notre
+              notre offre est conçue pour s’adapter à toutes les tailles
+              d'établissement. Le coût de l’abonnement au logiciel s’ajuste en
+              fonction du nombre d'élèves, tout en vous garantissant l’accès à
+              l'intégralité des fonctionnalités !
             </p>
 
             <v-btn to="nous-contacter" class="btn-contact">
@@ -44,7 +49,8 @@
           <LayoutUITitle> Bénéficiez de conditions privilégiées </LayoutUITitle>
 
           <div class="pl-4">
-            <v-img v-if="mdAndDown"
+            <v-img
+              v-if="mdAndDown"
               src="/images/logos/cmf/Logo_Confederation_Musicale_de_France-CMF_vivre_la_musique_ensemble.jpg"
               alt="Logo Confédération Musicale de France - CMF avec son slogan : vivre la musique ensemble"
               class="logo-cmf my-6"
@@ -137,7 +143,6 @@ const { smAndDown, mdAndDown, mdAndUp } = useDisplay()
   line-height: 20px;
   color: var(--on-neutral-color);
   max-width: 80%;
-
 }
 
 @media (max-width: 1240px) {

+ 598 - 0
components/Common/CookiesConsent.vue

@@ -0,0 +1,598 @@
+<template>
+  <div v-if="layoutStore.isCookieConsentDialogVisible">
+    <div v-if="!showCustomizationOptions" class="cookie-consent-banner">
+      <div class="continue-wrapper">
+        <a class="continue" href="#" @click.prevent="continueWithoutAccepting">
+          Continuer sans accepter
+        </a>
+        <v-icon
+          size="20"
+          class="fa-solid fa-arrow-right ml-6"
+          @click="closePopup()"
+        />
+      </div>
+      <v-row justify="center">
+        <v-col cols="12">
+          <img
+            src="/images/components/cookie-consent/A_cute_and_beautiful_illustration_of_a_cookie_list-removebg-preview.png"
+            alt="Cookie"
+            class="cookie-image"
+          />
+        </v-col>
+      </v-row>
+      <v-row no-gutters>
+        <v-col cols="12">
+          <div class="text">
+            <p>GESTION DES COOKIES</p>
+          </div>
+        </v-col>
+      </v-row>
+      <p class="details-cookies" style="padding-left: 20px">
+        Le site Opentalent.fr utilise des cookies fonctionnels nécessaires à la
+        navigation du site et d'autres technologies similaire pour plusieurs
+        objectifs : des cookies d'analyse de l'audience du site, des cookies de
+        personnalisation de contenu et des cookies publicitaires. Pour plus de
+        détail, veuillez consulter notre
+        <NuxtLink to="/politique-de-confidentialite#cookie-policy">
+          Politique de confidentialité</NuxtLink
+        >. Vous pouvez ajuster vos préférences en matière de cookies à tout
+        moment en cliquant sur le bouton "Gérer mes préférences"
+      </p>
+      <div class="horizontal-line"></div>
+      <div class="actions">
+        <button
+          class="customize-button"
+          @click="showCustomizationOptions = true"
+        >
+          Gérer mes préférences
+        </button>
+        <button class="accept-button" @click="acceptAllCookies">
+          Tout accepter
+        </button>
+        <!-- <button class="decline-button" @click="declineCookies">Refuser</button> -->
+      </div>
+    </div>
+
+    <v-dialog v-model="showCustomizationOptions" persistent max-width="600px">
+      <v-card>
+        <v-row class="headline">
+          <v-btn
+            class="close-dialog"
+            :icon="true"
+            @click="showCustomizationOptions = false"
+          >
+            <v-icon size="20" class="fas fa-times" />
+          </v-btn>
+
+          <v-card-title>Gérer mes préferences</v-card-title>
+        </v-row>
+
+        <p class="gestion-preferences">
+          Vous pouvez définir vos préférences sur la manière dont vous souhaitez
+          que vos données soient utilisées en fonction des finalités et des
+          entreprises tierces ci-dessous. Certains tiers peuvent traiter des
+          données sur la base d'un intérêt légitime et vous pouvez choisir de
+          vous désinscrire.
+        </p>
+
+        <v-container class="preferences-actions text-end">
+          <button class="decline-button" @click="declineCookies">
+            Tout refuser
+          </button>
+          <button class="accept-button" @click="acceptAllCookies">
+            Tout accepter
+          </button>
+        </v-container>
+
+        <v-card-text>
+          <h4>Des cookies tiers permettant de réaliser des statistiques</h4>
+
+          <v-row align="center">
+            <v-col cols="auto">
+              <v-switch
+                v-model="cookiesPreferences.analyticsConsent"
+                label="Mesure d'audience"
+                hide-details
+                color="green"
+                inset
+              />
+            </v-col>
+            <v-col>{{
+              cookiesPreferences.analyticsConsent ? 'Autorisé' : 'Non-autorisé'
+            }}</v-col>
+          </v-row>
+          <p>
+            Ces cookies nous permettent d'établir des statistiques, des volumes
+            de fréquentation et d'utilisation des divers éléments de notre site,
+            nous permettant d’optimiser son fonctionnement.
+          </p>
+
+          <h4 class="mt-6">Des cookies tiers à visée publicitaire</h4>
+
+          <v-row align="center">
+            <v-col cols="auto">
+              <v-switch
+                v-model="cookiesPreferences.advertisingConsent"
+                label="Suivi publicitaire"
+                hide-details
+                color="green"
+                inset
+              />
+            </v-col>
+            <v-col>{{
+              cookiesPreferences.advertisingConsent
+                ? 'Autorisé'
+                : 'Non-autorisé'
+            }}</v-col>
+          </v-row>
+          <p>
+            Autoriser le stockage et l'accès aux cookies pour diffuser des
+            annonces publicitaires personnalisées. Cela permet de vous montrer
+            des publicités plus pertinentes.
+          </p>
+
+          <v-row align="center">
+            <v-col cols="auto">
+              <v-switch
+                v-model="cookiesPreferences.adUserDataConsent"
+                label="Données Utilisateur pour la Publicité"
+                hide-details
+                color="green"
+                inset
+              />
+            </v-col>
+            <v-col>{{
+              cookiesPreferences.advertisingConsent
+                ? 'Autorisé'
+                : 'Non-autorisé'
+            }}</v-col>
+          </v-row>
+          <p>
+            Autoriser la collecte et l'utilisation de vos données personnelles
+            (comme votre adresse e-mail ou numéro de téléphone) pour créer des
+            profils de publicité personnalisés et améliorer le ciblage des
+            annonces.
+          </p>
+
+          <v-row align="center">
+            <v-col cols="auto">
+              <v-switch
+                v-model="cookiesPreferences.adPersonalizationConsent"
+                label="Personnalisation des annonces"
+                hide-details
+                color="green"
+                inset
+              />
+            </v-col>
+            <v-col>{{
+              cookiesPreferences.advertisingConsent
+                ? 'Autorisé'
+                : 'Non-autorisé'
+            }}</v-col>
+          </v-row>
+          <p>
+            Autoriser la personnalisation des annonces en fonction de vos
+            préférences et de votre comportement de navigation pour vous offrir
+            des publicités plus pertinentes et ciblées.
+          </p>
+        </v-card-text>
+
+        <v-card-actions>
+          <v-btn color="secondary" @click="showCustomizationOptions = false">
+            Fermer
+          </v-btn>
+
+          <v-spacer />
+
+          <button class="accept-button" @click="saveCookiesPreferences">
+            Sauvegarder
+          </button>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+  </div>
+  <v-alert
+    v-model="showNotification"
+    title="Confirmation"
+    type="warning"
+    width="400"
+    closable
+    transition="fade-transition"
+    density="compact"
+    class="alert"
+  >
+    Vous avez refusé nos cookies. Si vous le souhaitez, vous pouvez encore
+    modifier votre décision en
+    <a href="#" @click="showPopup()">cliquant ici</a>.
+  </v-alert>
+</template>
+
+<script setup lang="ts">
+import { onMounted, type Ref, ref } from 'vue'
+import { useCookies } from 'vue3-cookies'
+import { COOKIE_CONSENT_CHOICE } from '~/types/enum/enums'
+import type { CookiesPreferences } from '~/types/interface'
+
+const layoutStore = useLayoutStore()
+
+const { cookies } = useCookies()
+const showCustomizationOptions = ref(false)
+const showNotification = ref(false)
+
+const { gtag, initialize: initializeGTag } = useGtag()
+
+/**
+ * Cookies options
+ */
+const cookiesPreferences: Ref<CookiesPreferences> = ref({
+  consent: COOKIE_CONSENT_CHOICE.NONE,
+  analyticsConsent: true,
+  advertisingConsent: true,
+  adUserDataConsent: true,
+  adPersonalizationConsent: true,
+})
+
+/**
+ * Affiche la popup de choix de consentement aux cookies
+ */
+const showPopup = () => {
+  layoutStore.setIsCookieConsentDialogVisible(true)
+}
+
+/**
+ * Ferme la popup de choix de consentement aux cookies
+ */
+const closePopup = () => {
+  layoutStore.setIsCookieConsentDialogVisible(false)
+  showCustomizationOptions.value = false
+  showNotification.value = false
+}
+
+/**
+ * Affiche une notification d'une minute pour permettre à l'utilisateur qui
+ * a refusé les cookies de changer d'avis
+ */
+const notify = () => {
+  showNotification.value = true
+
+  // Hide the notification after 1 minute
+  setTimeout(() => {
+    showNotification.value = false
+  }, 60000)
+}
+
+/**
+ * Créé ou supprime les cookies selon les préférences en cours
+ *
+ * @param duration Durée de vie des cookies (en jours, défaut : 365)
+ */
+const setupCookies = (duration: number = 365) => {
+  // Enregistre les préférences actuelles dans 2 cookies
+  cookies.set(
+    'cookie_consent',
+    cookiesPreferences.value.consent,
+    duration + 'd'
+  )
+
+  cookies.set(
+    'cookie_preferences',
+    JSON.stringify({
+      analyticsConsent: cookiesPreferences.value.analyticsConsent,
+      advertisingConsent: cookiesPreferences.value.advertisingConsent,
+      adUserDataConsent: cookiesPreferences.value.adUserDataConsent,
+      adPersonalizationConsent:
+        cookiesPreferences.value.adPersonalizationConsent,
+    }),
+    duration + 'd'
+  )
+
+  // Initialise et paramètre google tag manager
+  initializeGTag()
+
+  gtag('consent', 'update', {
+    analytics_storage: cookiesPreferences.value.analyticsConsent
+      ? 'granted'
+      : 'denied',
+    ad_storage: cookiesPreferences.value.advertisingConsent
+      ? 'granted'
+      : 'denied',
+    ad_user_data: cookiesPreferences.value.adUserDataConsent
+      ? 'granted'
+      : 'denied',
+    ad_personalization: cookiesPreferences.value.adPersonalizationConsent
+      ? 'granted'
+      : 'denied',
+  })
+
+  // Nettoie les cookies si ceux ci ne sont plus les bienvenus
+  // TODO: voir si ce nettoyage manuel est nécessaire, ou si google tag manager ne peut pas s'en occuper
+  if (!cookiesPreferences.value.analyticsConsent) {
+    purgeAnalyticsCookies()
+  }
+  if (!cookiesPreferences.value.advertisingConsent) {
+    purgeAdvertisingCookies()
+  }
+
+  // Enregistre la date et le contenu de la dernière acceptation (comme trace)
+  localStorage.setItem(
+    'cookie_consent',
+    JSON.stringify({
+      date: new Date(),
+      consent: cookiesPreferences.value,
+    })
+  )
+}
+
+/**
+ * Nettoie les cookies google analytics
+ */
+const purgeAnalyticsCookies = () => {
+  cookies.remove('_ga')
+  cookies.remove('_gat')
+  cookies.remove('_gid')
+  cookies.keys().forEach((name) => {
+    if (/_ga_\w{10}/.test(name)) {
+      cookies.remove(name)
+    }
+  })
+}
+
+/**
+ * Nettoie les cookies meta pixel
+ */
+const purgeAdvertisingCookies = () => {
+  cookies.remove('_fbp')
+}
+
+/**
+ * Accept and setup all the cookies and close the popup
+ */
+const acceptAllCookies = () => {
+  cookiesPreferences.value = {
+    consent: COOKIE_CONSENT_CHOICE.ACCEPTED,
+    analyticsConsent: true,
+    advertisingConsent: true,
+    adUserDataConsent: true,
+    adPersonalizationConsent: true,
+  }
+
+  setupCookies()
+  closePopup()
+}
+
+/**
+ * Refuse all the cookies, set up the cookie_consent cookie and close the popup
+ */
+const declineCookies = () => {
+  cookiesPreferences.value = {
+    consent: COOKIE_CONSENT_CHOICE.DECLINED,
+    analyticsConsent: false,
+    advertisingConsent: false,
+    adUserDataConsent: false,
+    adPersonalizationConsent: false,
+  }
+
+  setupCookies(7)
+  notify()
+  closePopup()
+}
+
+/**
+ * Set up the cookies following user preferences and close the popup
+ */
+const saveCookiesPreferences = () => {
+  cookiesPreferences.value.consent = COOKIE_CONSENT_CHOICE.CUSTOMIZED
+  setupCookies()
+  closePopup()
+}
+
+/**
+ * Continue without accepting cookies
+ */
+const continueWithoutAccepting = () => {
+  closePopup()
+}
+
+/**
+ * Charge les préférences depuis les cookies
+ */
+const loadActivePreferences = () => {
+  const cookieConsentVal = cookies.get('cookie_consent')
+  const cookiePreferencesVal = cookies.get('cookie_preferences')
+
+  if (cookieConsentVal && cookiePreferencesVal) {
+    cookiesPreferences.value = {
+      consent: cookieConsentVal,
+      // @ts-ignore
+      ...cookiePreferencesVal,
+    }
+  }
+}
+
+/**
+ * Check if the user has already accepted the cookies when page is mounted
+ */
+onMounted(() => {
+  loadActivePreferences()
+  if (cookiesPreferences.value.consent === COOKIE_CONSENT_CHOICE.NONE) {
+    showPopup()
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.gestion-preferences {
+  font-size: 1.2rem;
+  font-weight: 500;
+  margin-bottom: 10px;
+  padding: 20px;
+  text-align: justify;
+}
+
+.preferences-actions {
+  overflow: visible;
+}
+
+.headline {
+  font-size: 1.5rem;
+  font-weight: 500;
+  margin: 0 0 10px 0;
+  background-color: var(--secondary-color);
+  color: var(--on-secondary-color);
+  text-transform: uppercase;
+  padding: 15px;
+}
+
+.cookie-consent-banner {
+  background: var(--neutral-color);
+  position: fixed;
+  bottom: 10px;
+  left: 15px;
+  max-width: 550px;
+  z-index: 1000 !important;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
+  border-radius: 15px;
+  padding: 20px;
+}
+
+.cookie-image {
+  width: 150px;
+  margin: 0;
+}
+
+.text {
+  padding-top: 10px;
+  padding-bottom: 0;
+  font-weight: 600;
+  font-size: 1.3rem;
+  text-align: center;
+}
+
+.details-cookies {
+  padding: 10px;
+  text-align: justify;
+}
+
+.horizontal-line {
+  width: 90%;
+  height: 1px;
+  background-color: var(--neutral-color-alt-strong);
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+.actions {
+  margin-top: 10px;
+  margin-bottom: 10px;
+  display: flex;
+  justify-content: center;
+}
+
+.accept-button,
+.customize-button {
+  background-color: var(--on-primary-color-alt);
+  border: none;
+  padding: 10px 20px;
+  margin: 5px;
+  cursor: pointer;
+}
+
+.decline-button {
+  border: 1px solid var(--on-neutral-color);
+  padding: 10px 20px;
+  margin: 5px;
+  cursor: pointer;
+}
+
+.accept-button:hover,
+.customize-button:hover {
+  background-color: var(--on-primary-color-alt);
+}
+
+.cookie-description {
+  margin: 0;
+  font-size: 0.875rem;
+  color: var(--on-neutral-color-light);
+}
+
+.custom-switch .v-input--selection-controls__ripple .v-ripple__container {
+  background-color: var(--v-primary-color);
+}
+
+.custom-switch,
+.v-input--selection-controls__ripple--active,
+.v-ripple__container {
+  background-color: var(--v-primary-darken4);
+}
+
+.custom-switch .v-input--selection-controls__input {
+  --v-theme-primary: var(--v-primary-base);
+  --v-theme-primary-lighten4: var(--v-primary-lighten4);
+  --v-theme-primary-darken4: var(--v-primary-darken4);
+}
+
+.custom-switch,
+.v-input--selection-controls__input,
+input:checked,
+.v-input--selection-controls__ripple,
+.v-ripple__container {
+  background-color: var(--v-primary-darken4);
+}
+
+.custom-switch,
+.v-input--selection-controls__input,
+input:checked,
+.v-input--selection-controls__ripple,
+.v-ripple__container,
+.v-ripple__animation {
+  background-color: var(--v-primary-darken4);
+}
+
+:deep(.v-switch__track) {
+  background-color: var(--warning-color);
+}
+
+.close-dialog {
+  background: none !important;
+  box-shadow: none !important;
+}
+
+.continue {
+  font-size: 0.9rem;
+  font-weight: 500;
+  cursor: pointer;
+  text-decoration: none !important;
+  color: var(--on-neutral-color);
+}
+
+.continue-wrapper {
+  display: flex;
+  justify-content: end;
+  align-items: center;
+  margin-left: auto;
+}
+
+:deep(.v-switch .v-label) {
+  opacity: 0.8;
+}
+
+.alert {
+  position: fixed;
+  bottom: 20px;
+  right: 20px;
+  z-index: 1000;
+
+  a {
+    color: var(--on-primary-color);
+    font-weight: 700;
+    text-decoration: none;
+  }
+
+  a:hover {
+    text-decoration: underline;
+  }
+}
+</style>

+ 558 - 0
components/Common/Error/NotFound.vue

@@ -0,0 +1,558 @@
+<template>
+  <div>
+    <svg
+      id="svg2"
+      viewBox="0 0 541.17206 328.45184"
+      height="328.45184"
+      width="541.17206"
+      version="1.1"
+    >
+      <metadata id="metadata8"></metadata>
+      <defs id="defs6">
+        <pattern
+          id="Strips2_1"
+          patternUnits="userSpaceOnUse"
+          width="1.5"
+          height="1"
+          patternTransform="translate(0,0) scale(10,10)"
+        >
+          <rect
+            id="rect5419"
+            style="fill: black; stroke: none"
+            x="0"
+            y="-0.5"
+            width="1"
+            height="2"
+          />
+        </pattern>
+        <linearGradient id="linearGradient6096" osb:paint="solid">
+          <stop
+            id="stop6094"
+            offset="0"
+            style="stop-color: #000000; stop-opacity: 1"
+          />
+        </linearGradient>
+      </defs>
+      <g id="layer1" transform="translate(170.14515,0.038164)">
+        <g id="g6219">
+          <path
+            id="path6180"
+            transform="matrix(1.0150687,0,0,11.193923,-1.3895945,-2685.7441)"
+            style="
+              display: inline;
+              fill: #000000;
+              fill-opacity: 1;
+              stroke: #000000;
+              stroke-width: 0.1px;
+              stroke-linecap: butt;
+              stroke-linejoin: miter;
+              stroke-opacity: 1;
+            "
+            d="m 145.0586,263.51309 c -90.20375,-0.0994 -119.20375,-0.0994 -119.20375,-0.0994"
+          />
+          />
+          <g id="g6174">
+            <ellipse
+              id="path4488"
+              ry="9.161705"
+              rx="9.3055239"
+              cy="91.32917"
+              cx="84.963676"
+              style="
+                display: inline;
+                opacity: 1;
+                fill: none;
+                fill-opacity: 0.4627451;
+                fill-rule: nonzero;
+                stroke: #000000;
+                stroke-width: 1.08691013;
+                stroke-miterlimit: 4;
+                stroke-dasharray: none;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4490"
+              d="m 84.984382,-0.03816399 c 0.911733,-5.0186e-4 1.661858,18.47051499 1.674386,41.22988399 0.0069,12.610431 -0.214009,23.904598 -0.56753,31.469836 -0.282878,6.088471 -0.652275,9.761785 -1.058838,9.762119 -0.406564,3.33e-4 -0.78198,-3.672386 -1.074838,-9.760657 -0.36185,-7.564779 -0.595233,-18.858715 -0.602175,-31.469228 -0.01253,-22.759565 0.717262,-41.23145213 1.628995,-41.23195399 z"
+              style="
+                display: inline;
+                fill: #000000;
+                stroke: none;
+                stroke-width: 0.23743393px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4496"
+              d="m 85.115421,100.5729 c -0.0036,3.37532 -0.0071,6.75165 -0.0107,10.12897 m 0.512159,0.18258 c -1.914603,-0.23621 -3.505591,1.17801 -4.861444,2.68113 -1.355853,1.50312 -2.473764,3.09173 -3.387866,4.59538 -0.914103,1.50365 -1.620209,2.91586 -2.416229,4.41952 -0.79602,1.50365 -1.67928,3.09352 -0.808656,3.24054 0.870624,0.14702 3.490408,-1.14815 5.700074,-1.91396 2.209666,-0.76581 4.001473,-1.00079 5.922125,-0.86765 1.920652,0.13314 3.947462,0.6325 6.245357,1.6195 2.297896,0.98701 4.861161,2.46015 4.9051,0.91309 0.04394,-1.54706 -2.430929,-6.11379 -4.787811,-9.33976 -2.356882,-3.22597 -4.596047,-5.11158 -6.51065,-5.34779 z"
+              style="
+                display: inline;
+                fill: #000000;
+                fill-opacity: 1;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <rect
+              id="rect4553"
+              ry="5"
+              y="314.84082"
+              x="35.355339"
+              height="9.8994951"
+              width="100.76272"
+              style="
+                display: inline;
+                opacity: 1;
+                fill: #000000;
+                fill-opacity: 1;
+                fill-rule: nonzero;
+                stroke: #000000;
+                stroke-width: 1.00157475;
+                stroke-miterlimit: 4;
+                stroke-dasharray: none;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4513"
+              d="m 74.6875,125.03748 c -8.394789,7.68654 -16.790624,15.37405 -23.988969,22.38484 -7.198345,7.0108 -13.197555,13.3433 -18.781379,20.01048 -5.583823,6.66719 -10.749655,13.66605 -13.916608,18.7496 -3.166952,5.08355 -4.333432,8.24971 -4.750315,11.08369 -0.416883,2.83399 -0.08368,5.33304 1.809372,16.25302 1.893048,10.91998 5.343489,30.24673 9.760132,48.66349 4.416642,18.41676 9.798356,35.91675 15.180267,53.41738"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4517"
+              d="m 76.9375,124.66248 c -4.548745,6.50695 -9.29087,13.29053 -13.530749,18.69724 -4.239879,5.4067 -8.072459,9.57255 -11.572943,13.98975 -3.500484,4.41719 -6.66636,9.08269 -9.333429,13.99996 -2.66707,4.91727 -4.833205,10.08267 -6.333458,15.08327 -1.500252,5.0006 -2.33339,9.8328 -2.500149,14.33343 -0.166759,4.50062 0.333124,8.66631 1.249922,15.50064 0.916798,6.83434 2.249854,16.33237 3.499902,24.91604 1.250047,8.58368 2.416611,16.24967 4.583438,28.58394 2.166827,12.33427 5.333153,29.33244 8.499966,46.33323"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4521"
+              d="m 96.8125,126.22498 c 6.89586,6.45836 13.7917,12.9167 19.98957,19.14581 6.19786,6.22912 11.69789,12.22914 17.11456,18.39581 5.41666,6.16667 10.74996,12.49995 14.74993,17.91655 3.99997,5.41659 6.66659,9.91653 7.16671,17.83316 0.50012,7.91664 -1.16644,19.24921 -3.3502,31.24619 -2.18376,11.99698 -4.81616,24.33632 -8.42063,38.99809 -3.60448,14.66177 -8.06212,31.17154 -12.56244,47.83939"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4525"
+              d="m 91.9375,124.09998 c 5.854072,7.16655 11.70824,14.33322 16.21863,20.16651 4.51039,5.83328 7.67706,10.33329 11.92718,16.33346 4.25012,6.00017 9.58322,13.49984 12.66653,18.58299 3.08332,5.08314 3.91663,7.74974 4.68205,10.91384 0.76542,3.1641 1.40129,6.50242 1.69781,8.02406 0.29651,1.52165 0.22299,1.06579 0.14933,0.60912"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4533"
+              d="m 89,123.66248 c 6.159885,11.51771 12.31996,23.03577 16.83724,31.78904 4.51728,8.75327 7.29964,14.54985 9.24424,18.32123 1.9446,3.77138 3.00519,5.42118 4.1838,9.19262 1.17861,3.77144 2.47477,9.6631 1.94443,23.80647 -0.53034,14.14338 -2.88706,36.53226 -5.4209,56.44951 -2.53383,19.91725 -5.24428,37.35836 -7.95503,54.80146"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4537"
+              d="m 87.0625,123.03748 c 2.916637,10.42937 5.833458,20.8594 7.291964,26.66356 1.458505,5.80416 1.458505,6.98257 2.402021,11.11052 0.943517,4.12795 2.827535,11.19302 4.065005,16.02501 1.23748,4.832 1.82668,7.42447 2.12139,10.84263 0.29471,3.41815 0.29471,7.65958 -0.11785,20.44893 -0.41255,12.78934 -1.23731,34.11536 -2.18014,53.62015 -0.94282,19.50478 -2.003429,37.18159 -3.064154,54.86032"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4541"
+              d="m 85.206367,122.98266 c 0.117841,11.74369 0.235693,23.48835 0.235693,36.55072 -10e-7,13.06238 -0.117833,27.43796 -0.05891,45.3521 0.05892,17.91413 0.29461,39.36153 0.707091,58.80738 0.412482,19.44585 1.001711,36.88701 1.590999,54.32995"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4545"
+              d="m 83.12978,122.92016 c -2.601311,10.56131 -5.214983,21.17282 -7.40283,31.41665 -2.187847,10.24384 -3.955407,20.14218 -5.074975,26.03483 -1.119568,5.89264 -1.59092,7.77805 -1.885708,10.07706 -0.294789,2.29901 -0.412567,5.0079 5.1e-5,17.56339 0.412617,12.55548 1.355064,34.93859 2.474996,54.74239 1.119932,19.80379 2.415574,37.00049 3.712005,54.20767"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4549"
+              d="m 79.25478,124.23266 c -5.440192,11.56251 -10.880951,23.12622 -15.899657,33.56368 -5.018706,10.43747 -9.614414,19.74672 -11.912808,26.70033 -2.298394,6.95362 -2.298394,11.54922 -1.355419,24.57415 0.942974,13.02493 2.828182,34.46917 5.066095,53.84746 2.237913,19.37829 4.833109,36.71892 7.425959,54.04387"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4556"
+              d="m 42.426407,155.38825 c 3.4184,0.82513 6.836082,1.65009 10.606997,2.18034 3.770916,0.53024 7.89657,0.76599 11.608535,0.88382 3.711965,0.11782 7.012548,0.11782 10.429711,0.0589 3.417163,-0.0589 6.953769,-0.17681 10.606588,-0.23572 3.652818,-0.0589 7.425155,-0.0589 11.137027,-0.23569 3.711875,-0.17679 7.366225,-0.53043 10.724475,-0.70716 3.35826,-0.17672 6.4233,-0.17672 9.48702,-0.58922 3.06372,-0.41251 6.12885,-1.23774 9.1918,-2.06238"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4560"
+              d="m 13.113199,198.16821 c 47.547038,0.40361 95.093071,0.80721 142.638101,1.2108"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1.00614154px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4529"
+              d="m 132.6875,263.34998 c -4.2289,18.4155 -8.45806,36.83216 -12.6875,55.25"
+              style="
+                display: inline;
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <ellipse
+              id="path4614"
+              ry="4.6715717"
+              rx="2.5"
+              cy="238.08525"
+              cx="119.12262"
+              style="
+                display: inline;
+                opacity: 1;
+                fill: #000000;
+                fill-opacity: 1;
+                fill-rule: nonzero;
+                stroke: #000000;
+                stroke-width: 1.00157475;
+                stroke-miterlimit: 4;
+                stroke-dasharray: none;
+                stroke-opacity: 1;
+              "
+            />
+            <ellipse
+              id="path4616"
+              ry="4.3158579"
+              rx="4.9001703"
+              cy="4.3948641"
+              cx="85.016434"
+              style="
+                display: inline;
+                opacity: 1;
+                fill: #000000;
+                fill-opacity: 1;
+                fill-rule: nonzero;
+                stroke: #000000;
+                stroke-width: 0.82170224;
+                stroke-miterlimit: 4;
+                stroke-dasharray: none;
+                stroke-opacity: 1;
+              "
+            />
+            <ellipse
+              id="path4565"
+              transform="translate(-170.14515,-0.038164)"
+              ry="3.880542"
+              rx="3.5777507"
+              cy="164.5713"
+              cx="321.42224"
+              style="
+                opacity: 1;
+                fill: #000000;
+                fill-opacity: 1;
+                fill-rule: nonzero;
+                stroke: #000000;
+                stroke-width: 1.00157475;
+                stroke-miterlimit: 4;
+                stroke-dasharray: none;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4567"
+              transform="translate(-170.14515,-0.038164)"
+              d="m 321.74355,168.0687 c -1e-5,3.3913 -3.42414,11.26702 -8.73834,11.26702 -5.3142,0 -18.59463,27.24606 -8.38477,3.759 1.35199,-3.11016 5.69513,-12.89881 10.50609,-15.15612 8.05545,-3.77965 6.61702,-3.26121 6.61702,0.1301 z"
+              style="
+                opacity: 1;
+                fill: #000000;
+                fill-opacity: 1;
+                fill-rule: nonzero;
+                stroke: #000000;
+                stroke-width: 1.00157475;
+                stroke-miterlimit: 4;
+                stroke-dasharray: none;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4570"
+              transform="translate(-170.14515,-0.038164)"
+              d="m 325,163.45184 c 1.66722,0.62594 3.33388,1.25167 3.33438,1.56444 5e-4,0.31276 -1.66671,0.31276 -3.33438,0.31276"
+              style="
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4578"
+              transform="translate(-170.14515,-0.038164)"
+              d="m 314.72098,177.37003 c -0.21488,1.64138 -0.42965,3.28197 0.28484,3.96351 0.71449,0.68155 2.35396,0.39999 3.99418,0.1183"
+              style="
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4578-1"
+              transform="translate(-170.14515,-0.038164)"
+              d="m 316,176.45184 c -0.29612,1.41007 -0.59214,2.81967 -0.25801,3.48764 0.33413,0.66798 1.29605,0.59017 2.25801,0.51236"
+              style="
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4610"
+              transform="translate(-170.14515,-0.038164)"
+              d="m 318,180.45184 c 0.66667,0 1.33434,0 1.501,0.16616 0.16667,0.16617 -0.16667,0.49951 0.001,0.66667 0.16767,0.16717 0.68771,0.16717 0.89053,0.36949 0.20282,0.20233 -0.0582,0.46335 -0.39253,0.79768"
+              style="
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4573"
+              d="m 155,199.59998 34.15106,6.52318 v 11.49049 l -1.06066,13.43503 -3.88908,19.44543 -3.00521,10.42983 -4.06586,12.19759 -17.14734,-4.94975 -14.92431,-4.65869 v 0 L 155,199.59998"
+              style="
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4575"
+              d="m 172.53405,202.94118 -2.65165,33.23402 -3.53553,16.97056 -5.12652,15.73313"
+              style="
+                fill: none;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+            <path
+              id="path4579"
+              d="m 187.2662,239.00256 c 0.76634,-0.82482 2.12163,-2.00333 3.50552,-2.26818 1.38389,-0.26485 2.79921,0.38383 3.2412,1.53192 0.442,1.14808 -0.0885,2.79852 -1.5624,3.24089 -1.4739,0.44236 -3.88809,-0.32312 -3.7995,0.001 0.0886,0.32427 2.68064,1.73812 4.00626,3.12221 1.32563,1.38408 1.38456,2.73956 0.79537,3.38822 -0.5892,0.64866 -1.82576,0.58977 -2.53349,0.11762 -0.70773,-0.47215 -0.88437,-1.35536 -1.59092,-2.65068 -0.70656,-1.29532 -1.94507,-3.00565 -2.47512,-4.09626 -0.53005,-1.09062 -0.35326,-1.56206 0.41308,-2.38689 z"
+              style="
+                fill: #000000;
+                fill-opacity: 1;
+                stroke: #000000;
+                stroke-width: 1px;
+                stroke-linecap: butt;
+                stroke-linejoin: miter;
+                stroke-opacity: 1;
+              "
+            />
+          </g>
+        </g>
+      </g>
+      <g id="layer3">
+        <g
+          id="text4526"
+          style="
+            fill: url(#Strips2_1);
+            fill-opacity: 1;
+            stroke: none;
+            stroke-width: 1.23488784;
+          "
+          transform="matrix(0.97168718,0,0,1.0291378,170.14515,0.038164)"
+          aria-label="4"
+        >
+          <path
+            id="path4555"
+            style="
+              fill: url(#Strips2_1);
+              fill-opacity: 1;
+              stroke: #000000;
+              stroke-width: 1.23488784;
+              stroke-opacity: 1;
+            "
+            d="M -0.46490841,256.59082 H -26.166013 v 43.5298 h -41.214384 v -43.5298 h -75.829833 l -9.95629,-15.28174 64.136994,-140.0826 h 8.914347 l 33.573515,15.8606 -48.507941,89.60655 -11.461305,19.56526 h 39.130513 l 4.399288,-43.06672 h 36.815096 v 43.06672 h 25.70110459 z"
+          />
+        </g>
+        <g
+          id="text4526-2"
+          style="
+            fill: url(#Strips2_1);
+            fill-opacity: 1;
+            stroke: none;
+            stroke-width: 1.23488784;
+          "
+          transform="matrix(0.97168718,0,0,1.0291378,377.95605,103.2934)"
+          aria-label="4"
+        >
+          <path
+            id="path4558"
+            style="
+              fill: url(#Strips2_1);
+              fill-opacity: 1;
+              stroke: #000000;
+              stroke-width: 1.23488784;
+              stroke-opacity: 1;
+            "
+            d="m 147.55592,156.33602 h -25.70111 v 43.5298 H 80.640431 v -43.5298 H 4.8105946 L -5.1456892,141.05429 58.991302,0.97168512 h 8.914347 L 101.47916,16.832277 52.971223,106.43883 41.50992,126.00409 h 39.130511 l 4.399288,-43.06672 h 36.815091 v 43.06672 h 25.70111 z"
+          />
+        </g>
+      </g>
+    </svg>
+
+    <p id="errorText">Oups ! Cette page n'existe pas...</p>
+  </div>
+</template>
+
+<style>
+main {
+  height: 100vh;
+  width: 100vw;
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+#errorText {
+  font-size: 22px;
+  margin: 28px 0;
+  font-family: 'Barlow', serif;
+  width: 100%;
+  text-align: center;
+}
+
+#g6219 {
+  transform-origin: 85px 4px;
+  animation: an1 12s 0.5s infinite ease-out;
+}
+
+@keyframes an1 {
+  0% {
+    transform: rotate(0);
+  }
+  5% {
+    transform: rotate(3deg);
+  }
+  15% {
+    transform: rotate(-2.5deg);
+  }
+  25% {
+    transform: rotate(2deg);
+  }
+  35% {
+    transform: rotate(-1.5deg);
+  }
+  45% {
+    transform: rotate(1deg);
+  }
+  55% {
+    transform: rotate(-1.5deg);
+  }
+  65% {
+    transform: rotate(2deg);
+  }
+  75% {
+    transform: rotate(-2deg);
+  }
+  85% {
+    transform: rotate(2.5deg);
+  }
+  95% {
+    transform: rotate(-3deg);
+  }
+  100% {
+    transform: rotate(0);
+  }
+}
+</style>

+ 9 - 9
components/Common/MenuScroll.vue

@@ -10,14 +10,6 @@ Menu de navigation entre les sections d'une page, accrochée au haut de l'écran
           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
@@ -27,6 +19,14 @@ Menu de navigation entre les sections d'une page, accrochée au haut de l'écran
               </v-list-item>
             </nuxt-link>
           </div>
+
+          <nuxt-link
+            v-if="isSticky"
+            :to="{ path: '', hash: '#top' }"
+            class="px-3 py-1"
+          >
+            <v-icon icon="fa fa-angle-up" />
+          </nuxt-link>
         </v-list>
       </v-col>
     </v-row>
@@ -100,7 +100,7 @@ const handleScroll = () => {
 .active {
   background-color: var(--scroll-menu-primary-color);
   color: var(--scroll-menu-on-primary-color);
-  border-radius: 16px;
+  border-radius: 16px !important;
   padding: 5px;
 }
 </style>

+ 1 - 1
components/Common/Presentation.vue

@@ -90,7 +90,7 @@ defineProps({
   section2title: {
     type: String,
     required: false,
-    default: 'Des caractéristiques uniques & dédiée',
+    default: 'Des caractéristiques uniques & dédiées',
   },
   features: {
     type: Object as PropType<Array<string>>,

+ 10 - 2
components/Common/ReviewSection.vue → components/Common/ReviewSection.client.vue

@@ -6,8 +6,16 @@
           <h3>Ce sont eux qui en parlent le mieux</h3>
 
           <div class="carousel-controls">
-            <v-btn icon="fas fa-chevron-left" @click="goPrevious" />
-            <v-btn icon="fas fa-chevron-right" @click="goNext" />
+            <v-btn
+              icon="fas fa-chevron-left"
+              aria-label="Précédent"
+              @click="goPrevious"
+            />
+            <v-btn
+              icon="fas fa-chevron-right"
+              aria-label="Suivant"
+              @click="goNext"
+            />
           </div>
         </div>
       </v-col>

+ 16 - 6
components/Common/Table/Comparatif.vue

@@ -8,20 +8,18 @@
 
             <th>
               <p class="standard">Standard</p>
-              <p class="from">À partir de</p>
               <p class="price">
                 {{ standardPrice }} <span class="ttc">ttc</span>
               </p>
-              <p class="month">/mois</p>
+              <p class="month">/mois *</p>
             </th>
 
             <th class="premium-column">
               <p class="standard">Premium</p>
-              <p class="from">À partir de</p>
               <p class="price">
                 {{ premiumPrice }} <span class="ttc">ttc</span>
               </p>
-              <p class="month">/mois</p>
+              <p class="month">/mois *</p>
             </th>
           </tr>
         </thead>
@@ -79,11 +77,13 @@
             :disabled="carouselPos === 0"
             icon="fas fa-chevron-left"
             class="mr-6"
+            aria-label="Précédent"
             @click="goToPrevious"
           />
           <v-btn
             :disabled="carouselPos === 1"
             icon="fas fa-chevron-right"
+            aria-label="Suivant"
             @click="goToNext"
           />
         </div>
@@ -176,6 +176,10 @@
           </v-carousel-item>
         </v-carousel>
       </div>
+
+      <div class="asterisk">
+        <span>* Payable annuellement</span>
+      </div>
     </v-row>
   </LayoutContainer>
 </template>
@@ -286,12 +290,10 @@ td:first-child {
   text-align: center;
   letter-spacing: 0.18em;
   text-transform: uppercase;
-  color: var(--primary-color);
   padding-right: 5rem;
   margin-bottom: 1rem;
 }
 
-.from,
 .ttc {
   font-weight: 300;
   font-size: 12px;
@@ -341,4 +343,12 @@ td:first-child {
     padding-right: 10px;
   }
 }
+
+.asterisk {
+  display: flex;
+  margin: 4px auto 0 auto;
+  width: 70%;
+  justify-content: flex-end;
+  font-style: italic;
+}
 </style>

+ 8 - 6
components/Contact/Form.vue

@@ -13,7 +13,7 @@
       <v-container>
         <i
           >Les champs dont le nom est suivi d'un astérisque (*) sont
-          obligatoires</i
+          obligatoires.</i
         >
 
         <h6>Vos coordonnées</h6>
@@ -21,10 +21,12 @@
         <!-- Gender selection -->
         <v-row>
           <v-col cols="12">
-            <v-radio-group v-model="contactRequest.gender" row mandatory inline>
-              <v-radio label="Madame" value="Madame" />
-              <v-radio label="Monsieur" value="Monsieur" />
-            </v-radio-group>
+            <v-select
+              v-model="contactRequest.gender"
+              label="Genre*"
+              :items="['Madame', 'Monsieur']"
+              mandatory
+            />
           </v-col>
         </v-row>
 
@@ -296,7 +298,7 @@ const errorMsg: Ref<string | null> = ref(null)
  * @returns {void}
  */
 const submit = async (): Promise<void> => {
-  const { valid } = await form.value.validate()
+  const { valid } = await form.value!.validate()
 
   if (!valid) {
     contactRequestSent.value = false

+ 5 - 1
components/Contact/Map.vue → components/Contact/Map.client.vue

@@ -130,8 +130,12 @@ h4 {
     opacity: 0.6;
   }
 
+  a {
+    color: var(--on-neutral-color);
+  }
+
   @media (max-width: 600px) {
-    order: -1
+    order: -1;
   }
 }
 </style>

+ 106 - 78
components/Formation/Catalogue.vue

@@ -1,81 +1,95 @@
 <template>
   <AnchoredSection id="catalog">
     <LayoutContainer>
-      <v-row class="center-90">
-        <LayoutUISubTitle>
-          Découvrez notre catalogue de formation
-        </LayoutUISubTitle>
+      <v-row class="background-block">
+        <v-row class="center-90">
+          <LayoutUISubTitle>
+            Découvrez notre catalogue de formation
+          </LayoutUISubTitle>
 
-        <LayoutUITitle class="ml-8"> Catalogue </LayoutUITitle>
-      </v-row>
+          <LayoutUITitle class="ml-8"> Catalogue </LayoutUITitle>
+        </v-row>
 
-      <v-row class="center-90 catalog">
-        <v-col v-for="(course, index) in courses" :key="index" cols="12" lg="4">
-          <v-card class="mb-4">
-            <v-card-text>
-              <div class="title-card-container">
-                <span class="number-card">
-                  {{ course.number }}
-                </span>
+        <v-row class="center-90 catalog">
+          <v-col
+            v-for="(course, index) in courses"
+            :key="index"
+            cols="12"
+            lg="4"
+          >
+            <v-card class="mb-4">
+              <v-card-text>
+                <div class="title-card-container">
+                  <span class="number-card">
+                    {{ course.number }}
+                  </span>
 
-                <h4>
-                  {{ course.title }}
-                </h4>
-              </div>
+                  <h4>
+                    {{ course.title }}
+                  </h4>
+                </div>
 
-              <p class="details-card mb-6">
-                {{ course.description }}
-              </p>
+                <p class="details-card mb-6">
+                  {{ course.description }}
+                </p>
 
-              <div class="objectives">
-                <h6 class="title-obj">Objectifs pédagogiques</h6>
+                <div class="objectives">
+                  <h6 class="title-obj">Objectifs pédagogiques</h6>
 
-                <ul>
-                  <li
-                    v-for="(objective, objIndex) in course.objectives"
-                    :key="objIndex"
-                  >
-                    {{ objective }}
-                  </li>
-                </ul>
-              </div>
+                  <ul>
+                    <li
+                      v-for="(objective, objIndex) in course.objectives"
+                      :key="objIndex"
+                    >
+                      {{ objective }}
+                    </li>
+                  </ul>
+                </div>
 
-              <div class="badge-time">Durée : {{ course.duration }}</div>
+                <div class="badge-time">Durée : {{ course.duration }}</div>
 
-              <div class="program">
-                <h6 class="title-obj">Programme</h6>
+                <div class="program">
+                  <h6 class="title-obj">Programme</h6>
 
-                <v-row>
-                  <v-col
-                    v-for="column in course.program"
-                    :key="column.id"
-                    cols="6"
-                  >
-                    <ul>
-                      <li
-                        v-for="(objective, objIndex) in column.objectives"
-                        :key="objIndex"
-                      >
-                        {{ objective }}
-                      </li>
-                    </ul>
-                  </v-col>
-                </v-row>
-              </div>
+                  <v-row>
+                    <v-col
+                      v-for="column in course.program"
+                      :key="column.id"
+                      cols="6"
+                    >
+                      <ul>
+                        <li
+                          v-for="(objective, objIndex) in column.objectives"
+                          :key="objIndex"
+                        >
+                          {{ objective }}
+                        </li>
+                      </ul>
+                    </v-col>
+                  </v-row>
+                </div>
 
-              <div class="badge-time">
-                {{ course.price }}
-              </div>
+                <div class="badge-time">
+                  {{ course.price }}
+                </div>
 
-              <v-chip
-                class="chip-register"
-                @click="downloadPdf(course.downloadLink)"
-              >
-                Télécharger le programme de formation
-              </v-chip>
-            </v-card-text>
-          </v-card>
-        </v-col>
+                <v-chip
+                  class="chip-register"
+                  @click="downloadPdf(course.downloadLink)"
+                >
+                  Télécharger le programme de formation
+                </v-chip>
+              </v-card-text>
+            </v-card>
+          </v-col>
+        </v-row>
+        <v-row class="center-90 font-weight-medium">
+          <v-banner color="info" lines="one" :stacked="false">
+            <v-icon icon="fas fa-circle-info" class="text-info mr-2" />
+            Les formations dispensées par 2iopenservice sont exonérées de TVA
+            (Art.261,4-4° CGI)
+          </v-banner>
+        </v-row>
       </v-row>
     </LayoutContainer>
   </AnchoredSection>
@@ -124,16 +138,15 @@ const courses: Array<Training> = [
         ],
       },
     ],
-    price: '2 008,80€ TTC',
-    downloadLink:
-      'public/files/PF-School-2024-02_2-jours.pdf',
+    price: '2 008,80€ HT',
+    downloadLink: 'files/PF-School-2024-02_2-jours.pdf',
   },
   {
     number: '02',
     title: 'Formation complémentaire',
     // imageUrl: "/images/opentalent_school_black.jpg",
     description:
-      'Cette formation suppose d’avoir les connaissances de base sur le logiciel. Elle permet d’avoir une remise à niveau sur sur des fonctionnalités qui ont été incorrectement comprises / configurées, ou qui ont été récemment développées.',
+      'Cette formation suppose d’avoir les connaissances de base sur le logiciel. Elle permet d’avoir une remise à niveau sur des fonctionnalités qui ont été incorrectement comprises / configurées, ou qui ont été récemment développées.',
     objectives: [
       'Ajuster la configuration du logiciel',
       'Gérer les élèves et leurs familles',
@@ -149,9 +162,8 @@ const courses: Array<Training> = [
         objectives: ['Répertoire'],
       },
     ],
-    price: '1004,40€ TTC',
-    downloadLink:
-      'public/files/PF-School-2024-02_1-jour.pdf',
+    price: '1004,40€ HT',
+    downloadLink: 'files/PF-School-2024-02_1-jour.pdf',
   },
   {
     number: '03',
@@ -160,8 +172,8 @@ const courses: Array<Training> = [
     description:
       "Cette formation est destinée aux nouveaux utilisateurs Typo3. Elle est optionnelle et permet d'aller plus loin dans la gestion du site internet intégré.",
     objectives: [
-      'Gérer les pages et leur accès',
-      'Gérer le contenu des pages et leur accès',
+      'Gérer les pages et leur accessibilité',
+      'Gérer le contenu des pages et leur accessibilité',
       'Configurer les options du site internet côté logiciel',
     ],
     duration: '7h',
@@ -175,18 +187,19 @@ const courses: Array<Training> = [
         objectives: ['Configuration côté logiciel'],
       },
     ],
-    price: '1004,40€ TTC',
-    downloadLink:
-      'public/files/PF-Typo3-2024-02_1-jour.pdf',
+    price: '1004,40€ HT',
+    downloadLink: 'files/PF-Typo3-2024-02_1-jour.pdf',
   },
 ]
 </script>
 
 <style scoped lang="scss">
-.catalog {
-  padding: 2rem;
+.background-block {
   background: var(--neutral-color-alt-light);
+  padding: 4rem;
+}
 
+.catalog {
   @media (max-width: 600px) {
     padding: 0.5rem;
   }
@@ -224,6 +237,10 @@ const courses: Array<Training> = [
     margin-top: 1rem;
     margin-bottom: 0.5rem;
     height: 5rem;
+
+    @media (max-width: 600px) {
+      height: auto;
+    }
   }
 
   .objectives,
@@ -279,6 +296,17 @@ const courses: Array<Training> = [
     display: flex;
     justify-content: center;
     align-items: center;
+
+    @media (max-width: 600px) {
+      height: 42px;
+
+      :deep(.v-chip__content) {
+        max-width: 100%;
+        overflow-wrap: break-word;
+        white-space: break-spaces;
+        text-align: center;
+      }
+    }
   }
 }
 </style>

+ 3 - 3
components/Formation/Certification.vue

@@ -11,7 +11,7 @@
 
           <div class="bloc-certif">
             <p>
-              Nos formations sont certifiées Qualiopi - Actions de formation.
+              Nos formations sont certifiées Qualiopi - Actions de formations.
             </p>
 
             <p class="details-certifications">
@@ -20,12 +20,12 @@
 
             <ul>
               <li>
-                attester de la qualité du processus mis en œuvre par les
+                Attester de la qualité du processus mis en œuvre par les
                 prestataires d’actions concourant au développement des
                 compétences ;
               </li>
               <li>
-                permettre une plus grande lisibilité de l’offre de formation
+                Permettre une plus grande lisibilité de l’offre de formation
                 auprès des entreprises et des usagers.
               </li>
             </ul>

+ 22 - 10
components/Formation/OPCA.vue

@@ -2,7 +2,9 @@
   <AnchoredSection id="financing">
     <LayoutContainer class="alt-theme py-6 my-6">
       <v-row class="center-90">
-        <LayoutUISubTitle> Financement des formations </LayoutUISubTitle>
+        <LayoutUISubTitle class="ml-4">
+          Financement des formations
+        </LayoutUISubTitle>
       </v-row>
 
       <v-container>
@@ -16,7 +18,7 @@
             />
           </v-col>
 
-          <v-col cols="12" md="6">
+          <v-col cols="12" md="6" class="col-details">
             <v-row>
               <h3 class="mt-6 mb-6 mr-6">
                 Des formations éligibles par votre OPCA
@@ -25,16 +27,22 @@
 
             <v-row>
               <div class="details-opca mr-6">
-                <p>
+                <p class="mb-4">
                   Les formations dispensées par Opentalent entrent dans le cadre
                   de la formation professionnelle continue et peuvent être pris
-                  en charge par votre OPCA (Uniformation, AFDAS, ...). Si votre
-                  formation est prise en charge par un organisme accrédité, des
-                  documents pourront vous être demandés (convention de
-                  formation...). Nous pouvons vous fournir tous les éléments
-                  nécessaires mais les démarches restent de votre
-                  responsabilité. Afin de réduire le coût,nos formations sont
-                  exonérées de TVA.
+                  en charge par votre OPCA (Uniformation, AFDAS, ...).
+                </p>
+
+                <p class="mb-4">
+                  Si votre formation est prise en charge par un organisme
+                  accrédité, des documents pourront vous être demandés
+                  (convention de formation...). Nous pouvons vous fournir tous
+                  les éléments nécessaires mais les démarches restent de votre
+                  responsabilité.
+                </p>
+
+                <p class="mb-4">
+                  Afin de réduire le coût, nos formations sont exonérées de TVA.
                 </p>
 
                 <p>
@@ -69,6 +77,10 @@ h3 {
   margin-top: 0.6rem;
 }
 
+.col-details {
+  max-width: 550px;
+}
+
 .details-opca {
   font-size: 16px;
   line-height: 20px;

+ 17 - 14
components/Formation/Participation.vue

@@ -25,26 +25,29 @@
           <div class="details-participations">
             <p class="mb-6">
               Nous vous répondons sous 48h (hors week-ends). Délais d'accès:
-              Après accord, nous mettons tout en oeuvre pour vous mettre à
-              disposition un formateur sous 1 mois."
+              après accord, nous mettons tout en oeuvre pour vous mettre à
+              disposition un formateur sous 1 mois.
             </p>
 
-            <p>
+            <p class="mb-6">
               Nos formations peuvent être accessibles aux personnes en situation
               de handicap. Chaque situation étant unique, nous vous demandons de
               préciser à l’inscription votre handicap. Nous pourrons ainsi
               confirmer l’ensemble des possibilités afin de vous permettre de
-              suivre la formation dans les meilleures conditions en accord avec
-              votre employeur. Pour toutes informations complémentaires, nous
-              vous conseillons les structures suivantes : ONISEP, AGEFIPH et
-              FIPHFP. Pour le bon déroulement de la formation, il est nécessaire
-              de disposer d’un espace de travail calme et équipé d’au moins un
-              ordinateur, connecté à internet, et dont le navigateur (Mozilla
-              Firefox, Google Chrome, ou Apple Safari) est à jour. Nous
-              recommandons d’utiliser un ordinateur par personne. Les
-              participants devront également avoir des ordinateurs équipés de
-              micros et de haut-parleurs, préférablement des micro-casques pour
-              un meilleur confort d’écoute.
+              suivre la formation dans les meilleures conditions, en accord avec
+              votre employeur.
+            </p>
+            <p>
+              Pour toutes informations complémentaires, nous vous conseillons
+              les structures suivantes : ONISEP, AGEFIPH et FIPHFP. Pour le bon
+              déroulement de la formation, il est nécessaire de disposer d’un
+              espace de travail calme et équipé d’au moins un ordinateur,
+              connecté à internet, et dont le navigateur (Mozilla Firefox,
+              Google Chrome, ou Apple Safari) est à jour. Nous recommandons
+              d’utiliser un ordinateur par personne. Les participants devront
+              également avoir des ordinateurs équipés de micros et de
+              haut-parleurs, préférablement des micro-casques pour un meilleur
+              confort d’écoute.
             </p>
           </div>
         </v-col>

+ 4 - 4
components/Formation/Presentation.vue

@@ -32,8 +32,8 @@
             Dans le cadre de notre solution
             <span class="manager">Opentalent Manager</span> , nous pouvons
             organiser, selon vos besoins, des sessions de formation et
-            initiation individuel, en groupe, pour des évènements particuliers
-            tels que des congrès, universités d’été, séminaires...
+            d'initiation individuelles, en groupe, pour des événements
+            particuliers tels que des congrès, séminaires...
           </p>
         </v-col>
       </v-row>
@@ -46,9 +46,9 @@
 
           <p class="details-programme">
             Afin de prendre en compte les évolutions de votre structure, nous
-            pouvons enseuite organiser des sessions de formations spécifiques
+            pouvons ensuite organiser des sessions de formations spécifiques
             pour répondre à vos besoins : outils complets, mise en route,
-            fonctionnalités spécifiques, utilisation du site web....
+            fonctionnalités spécifiques, utilisation du site web...
           </p>
         </v-col>
 

+ 1 - 1
components/Home/Besoin.vue

@@ -14,7 +14,7 @@
           La satisfaction de nos clients est notre première motivation et nous
           nous tenons à votre écoute pour faire évoluer notre logiciel. <br />
           <b>Un besoin ?</b> <br />
-          Notifiez le nous et après l'étude de votre demande en interne puis
+          Notifiez-le nous et après l'étude de votre demande en interne puis
           validation, nous intégrerons votre requête à notre processus de
           développement.
         </p>

+ 8 - 8
components/Home/Caroussel.vue

@@ -107,15 +107,15 @@ const carouselItems: Ref<Array<CarouselItem>> = ref([
     logoAlt:
       'Logo Opentalent School - logiciel de gestion et de communication pour les établissements d’enseignement artistique',
     description:
-      "Pour les petits comme pour les GRANDS établissements d’enseignement artistique tels que les écoles de musique, de danse, de théâtre, d'art, de cirque et conservatoire.<br> Il permet la gestion au quotidien et en temps réel de votre établissement, de gérer vos élèves et vos professeurs, vos emplois du temps, le suivi pédagogique, vos salles, la facturation et les encaissements…",
+      "Pour les petits comme pour les GRANDS établissements d’enseignement artistique tels que les écoles de musique, de danse, de théâtre, d'art, de cirque et conservatoires.<br> Il permet la gestion au quotidien et en temps réel de votre établissement, de gérer vos élèves et vos professeurs, vos emplois du temps, le suivi pédagogique, vos salles, la facturation et les encaissements…",
     descriptionSm:
-      "Pour les petits comme pour les GRANDS établissements d’enseignement artistique tels que les écoles de musique, de danse, de théâtre, d'art, de cirque et conservatoire.",
+      "Pour les petits comme pour les GRANDS établissements d’enseignement artistique tels que les écoles de musique, de danse, de théâtre, d'art, de cirque et conservatoires.",
     buttonClass: 'btn-school',
     image:
       '/images/pages/home/carousel/Logiciel_Opentalent_School-eleve-Conservatoire_de_Musique.png',
     imageAlt: 'Jeune élève de batterie',
     color: 'rgba(32, 147, 190, 0.4)',
-    link: '/opentalent_school',
+    link: '/opentalent-school',
     name: 'Cindy Blanchard',
     school: 'Conservatoire de Musique',
     status: 'élève',
@@ -127,15 +127,15 @@ const carouselItems: Ref<Array<CarouselItem>> = ref([
     logoAlt:
       'Logo Opentalent Artist - logiciel de gestion et de communication pour les orchestres, les chorales, les compagnies artistiques et troupes',
     description:
-      'Pour les structures culturelles pratiquantes telles que les orchestres, les chorales, les compagnies de danse, théâtre et cirque. <br> Gérez votre activité avec un logiciel de gestion et de communication au service de votre passion.',
+      'Pour les structures culturelles pratiquantes telles que les orchestres, les chorales, les compagnies de danse, de théâtre et de cirque. <br> Gérez votre activité avec un logiciel de gestion et de communication au service de votre passion.',
     descriptionSm:
-      'Pour les structures culturelles pratiquantes telles que les orchestres, les chorales, les compagnies de danse, théâtre et cirque.',
+      'Pour les structures culturelles pratiquantes telles que les orchestres, les chorales, les compagnies de danse, de théâtre et de cirque.',
     buttonClass: 'btn-artist',
     image:
       '/images/pages/home/carousel/Logiciel_Opentalent_Artist-administrateur-Orchestre_d_harmonie.png',
     imageAlt: 'Homme jouant du banjo',
     color: 'rgba(250, 194, 10, 0.4)',
-    link: 'opentalent_artist',
+    link: 'opentalent-artist',
     name: 'Thierry Dupont ',
     school: 'Orchestre d’harmonie',
     status: 'Admin',
@@ -148,7 +148,7 @@ const carouselItems: Ref<Array<CarouselItem>> = ref([
     logoAlt:
       'Logo Opentalent Manager - logiciel de gestion et de communication pour les fédérations, les confédérations et les collectivités',
     description:
-      'La solution de mise en réseau des organisations culturelles.<br> Fédérations, confédérations et collectivités, utilisez une solution collaborative innovante et unique spécialement développée pour les réseaux culturels.',
+      'La solution de mise en réseau des organisations culturelles.<br> Fédérations, confédérations et collectivités, adoptez une solution collaborative innovante et unique spécialement développée pour les réseaux culturels.',
     descriptionSm:
       'La solution de mise en réseau des organisations culturelles, telles que les fédérations, confédérations et collectivités.',
     buttonClass: 'btn-manager',
@@ -156,7 +156,7 @@ const carouselItems: Ref<Array<CarouselItem>> = ref([
       '/images/pages/home/carousel/Logiciel_Opentalent_Manager-Administrateur_Federation.png',
     imageAlt: 'Un homme en costard et une femme tailleur souriant',
     color: 'rgba(216, 5, 11, 0.4)',
-    link: 'opentalent_manager',
+    link: 'opentalent-manager',
     name: 'Marie Voisin',
     school: "Réseau d'organisations culturelles ",
     status: 'ADMIN',

+ 2 - 4
components/Home/EventAgenda.vue

@@ -5,9 +5,7 @@ Section "Agenda" de la page d'accueil
   <LayoutContainer>
     <div class="container">
       <div class="d-flex flex-row justify-center">
-        <h2 class="alt-theme bg-transparent">
-          L’agenda Opentalent
-        </h2>
+        <h2 class="alt-theme bg-transparent">L’agenda Opentalent</h2>
       </div>
 
       <div class="d-flex align-center justify-center">
@@ -59,7 +57,7 @@ const { lgAndUp } = useDisplay()
       rgba(14, 45, 50, 0.2) 100%
     ),
     linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
-    url('/images/components/agenda/Opentalent_Agenda_événement_culturel.jpg')
+    url('/images/components/agenda/Opentalent_Agenda_evenement_culturel.jpg')
       no-repeat center 60%;
   background-size: cover;
 

+ 32 - 28
components/Home/Promotion.vue

@@ -7,34 +7,37 @@
         </LayoutUISectionTitle>
 
         <v-row>
-          <p class="text-block">
-            <span class="highlight">Simplifiez</span>-vous la vie avec un outil
-
-            <span class="inline-pic-container">
-              <v-img
-                src="/images/pages/home/promotion/Visuel_d_un_Pianiste.png"
-                alt="Vignette d’une personne jouant sur un piano"
-              />
-            </span>
-
-            tout en un pour la gestion et la promotion
-
-            <span class="inline-pic-container">
-              <v-img
-                src="/images/pages/home/promotion/Visuel_de_Danseuse.png"
-                alt="Vignette d’une danseuse"
-              />
-            </span>
-
-            , de votre structure culturelle.
-
-            <span class="inline-pic-container">
-              <v-img
-                src="/images/pages/home/promotion/Visuel_d_un_Comedien.PNG"
-                alt="Vignette d’un homme faisant un spectacle de mime dans une salle de spectacle"
-              />
-            </span>
-          </p>
+          <v-col>
+            <div class="text-block">
+              <span class="highlight">Simplifiez</span>-vous la vie avec un
+              outil
+
+              <span class="inline-pic-container">
+                <v-img
+                  src="/images/pages/home/promotion/Visuel_d_un_Pianiste.png"
+                  alt="Vignette d’une personne jouant sur un piano"
+                />
+              </span>
+
+              tout-en-un pour la gestion et la promotion
+
+              <span class="inline-pic-container">
+                <v-img
+                  src="/images/pages/home/promotion/Visuel_de_Danseuse.png"
+                  alt="Vignette d’une danseuse"
+                />
+              </span>
+
+              de votre structure culturelle.
+
+              <span class="inline-pic-container">
+                <v-img
+                  src="/images/pages/home/promotion/Visuel_d_un_Comedien.PNG"
+                  alt="Vignette d’un homme faisant un spectacle de mime dans une salle de spectacle"
+                />
+              </span>
+            </div>
+          </v-col>
         </v-row>
       </v-col>
     </v-row>
@@ -174,6 +177,7 @@
       height: 10rem;
       width: 40rem;
       margin: 3rem auto 5rem;
+      max-width: 100%;
     }
 
     .v-img {

+ 12 - 4
components/Home/Reviews.vue → components/Home/Reviews.client.vue

@@ -46,7 +46,7 @@
                   je me suis sentie écoutée lors de mes propositions
                   d'amélioration, car beaucoup ont vu le jour. Enfin,
                   l'accompagnement et la réactivité n'ont jamais faibli depuis
-                  toutes ces années
+                  toutes ces années.
                 </p>
               </v-card-text>
 
@@ -75,7 +75,7 @@
                   le fait que l’équipe soit à l'écoute pour faire évoluer
                   l'outil en fonction de nos besoins. Si besoin, la FAQ est
                   vraiment utile. Elle permet de trouver rapidement une solution
-                  face à un problème rencontré..
+                  face à un problème rencontré.
                 </p>
               </v-card-text>
 
@@ -153,7 +153,11 @@
 
       <v-row class="justify-center align-center mb-8">
         <v-col class="d-flex justify-space-around align-center">
-          <v-btn icon="fas fa-arrow-left-long" @click="goToPrevious" />
+          <v-btn
+            icon="fas fa-arrow-left-long"
+            aria-label="Précédent"
+            @click="goToPrevious"
+          />
 
           <div class="carousel-controls">
             <div
@@ -164,7 +168,11 @@
             />
           </div>
 
-          <v-btn icon="fas fa-arrow-right-long" @click="goToNext" />
+          <v-btn
+            icon="fas fa-arrow-right-long"
+            aria-label="Suivant"
+            @click="goToNext"
+          />
         </v-col>
       </v-row>
 

+ 6 - 6
components/Home/Solution.vue

@@ -87,9 +87,9 @@ const solutions: Array<SolutionItem> = [
   {
     name: 'Artist',
     description: 'Orchestres, chorales, compagnies et troupes artistiques',
-    image: '/images/logos/Logo_Opentalent_Artist-blanc.png',
+    image: '/images/logos/opentalent/Logo_Opentalent_Artist-blanc.png',
     alt: 'Partition tenue par une femme dans une chorale',
-    link: '/opentalent_artist',
+    link: '/opentalent-artist',
     class: 'artist-image',
     solutions: [
       'Gestion des membres',
@@ -105,9 +105,9 @@ const solutions: Array<SolutionItem> = [
   {
     name: 'School',
     description: "Petits et grands établissements d'enseignement artistique",
-    image: '/images/logos/Logo_Opentalent_School-blanc',
+    image: '/images/logos/opentalent/Logo_Opentalent_School-blanc.png',
     alt: 'Deux jeunes filles jouant du violon',
-    link: '/opentalent_school',
+    link: '/opentalent-school',
     class: 'school-image',
     solutions: [
       'Gestion des membres',
@@ -123,9 +123,9 @@ const solutions: Array<SolutionItem> = [
   {
     name: 'Manager',
     description: 'Fédérations, confédérations et collectivités',
-    image: '/images/logos/Logo_Opentalent_Manager-blanc.png',
+    image: '/images/logos/opentalent/Logo_Opentalent_Manager-blanc.png',
     alt: 'Carte de réseau des structures de la confédération musicale de France',
-    link: '/opentalent_manager',
+    link: '/opentalent-manager',
     class: 'manager-image',
     solutions: [
       'Gestion des membres',

+ 68 - 0
components/JoinUs/Dialog.vue

@@ -0,0 +1,68 @@
+<!-- Boite de dialogue "soumettre une candidature" -->
+<template>
+  <v-dialog
+    :model-value="modelValue"
+    max-width="600px"
+    :persistent="!jobApplicationSent"
+    no-click-animation
+    :retain-focus="false"
+  >
+    <div v-if="!jobApplicationSent">
+      <JoinUsForm :job-posting-id="jobPostingId" @submit="onFormSubmit" />
+
+      <v-btn @click="close()"> 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="close()"> Fermer </v-btn>
+    </div>
+  </v-dialog>
+</template>
+
+<script setup lang="ts">
+import type { PropType } from 'vue'
+
+defineProps({
+  modelValue: Boolean,
+  jobPostingId: {
+    type: Number as PropType<number | null>,
+    required: false,
+    default: null,
+  },
+})
+
+const jobApplicationSent: Ref<boolean> = ref(false)
+
+const emit = defineEmits(['update:modelValue'])
+
+const onFormSubmit = () => {
+  jobApplicationSent.value = true
+}
+
+const close = () => {
+  emit('update:modelValue', false)
+}
+</script>
+
+<style scoped lang="scss">
+.v-dialog {
+  :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>

+ 43 - 22
components/JoinUs/Form.vue

@@ -42,7 +42,7 @@
           <v-file-input
             id="jobApplicationResume"
             v-model="resumeUpload"
-            :rules="[validateResume, validateFileSize]"
+            :rules="[validateResume, validateResumeFileSize]"
             label="Dépôt de CV*"
             accept=".pdf, .jpeg, .png"
             show-size
@@ -52,7 +52,10 @@
           <v-file-input
             id="jobApplicationMotivationLetter"
             v-model="motivationLetterUpload"
-            :rules="[validateFileSize]"
+            :rules="[
+              validateMotivationLetter,
+              validateMotivationLetterFileSize,
+            ]"
             label="Dépôt de lettre de motivation"
             accept=".pdf, .jpeg, .png"
             show-size
@@ -86,13 +89,21 @@
 </template>
 
 <script setup lang="ts">
-import type { ComputedRef, Ref } from 'vue'
+import type { ComputedRef, PropType, Ref } from 'vue'
 import { reactive } from 'vue'
 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 props = defineProps({
+  jobPostingId: {
+    type: Number as PropType<number | null>,
+    required: false,
+    default: null,
+  },
+})
+
 const { em } = useEntityManager()
 
 const form: Ref<HTMLElement | null> = ref(null)
@@ -134,13 +145,23 @@ const validatePhone = (email: string | null) =>
 
 const validateResume = () =>
   (resumeUpload.value !== null && resumeUpload.value[0] !== null) ||
-  "Vous devez joindre un CV à l'un des formats indiqués"
+  "Vous devez joindre un CV à l'un des formats suivants : .pdf, .jpeg, .png"
 
-const validateFileSize = () =>
+const validateResumeFileSize = () =>
   (resumeUpload.value !== null &&
-    resumeUpload.value[0] !== null &&
     // @ts-ignore
-    resumeUpload.value[0].size < maxFileSize * 1024 * 1024) ||
+    resumeUpload.value.size < maxFileSize * 1024 * 1024) ||
+  'La taille du fichier ne doit pas dépasser ' + maxFileSize + ' Mo'
+
+const validateMotivationLetter = () =>
+  motivationLetterUpload.value === null ||
+  motivationLetterUpload.value[0] !== null ||
+  "Vous devez joindre votre lettre de motivation à l'un des formats suivants : .pdf, .jpeg, .png"
+
+const validateMotivationLetterFileSize = () =>
+  motivationLetterUpload.value === null ||
+  // @ts-ignore
+  motivationLetterUpload.value.size < maxFileSize * 1024 * 1024 ||
   'La taille du fichier ne doit pas dépasser ' + maxFileSize + ' Mo'
 
 const validateNonEmptyMessage = (message: string | null) =>
@@ -154,34 +175,34 @@ const validateMessageLength = (message: string | null) =>
  * Soumet le formulaire de candidature (boite de dialogue)
  */
 const submit = async () => {
-  const { valid } = await form.value.validate()
-
-  if (!valid) {
-    jobApplicationSent.value = false
-    return
-  }
+  jobApplication.jobPostingId = props.jobPostingId
 
   jobApplication.resume =
-    resumeUpload.value !== null && resumeUpload.value[0] !== null
+    resumeUpload.value !== null
       ? {
           // @ts-ignore
-          name: resumeUpload.value[0].name,
-          content: await FileUtils.blobToBase64(resumeUpload.value[0]),
+          name: resumeUpload.value.name,
+          content: await FileUtils.blobToBase64(resumeUpload.value),
         }
       : null
 
   jobApplication.motivationLetter =
-    motivationLetterUpload.value !== null &&
-    motivationLetterUpload.value[0] !== null
+    motivationLetterUpload.value !== null
       ? {
           // @ts-ignore
-          name: motivationLetterUpload.value[0].name,
-          content: await FileUtils.blobToBase64(
-            motivationLetterUpload.value[0]
-          ),
+          name: motivationLetterUpload.value.name,
+          content: await FileUtils.blobToBase64(motivationLetterUpload.value),
         }
       : null
 
+  // @ts-ignore
+  const { valid } = await form.value!.validate()
+
+  if (!valid) {
+    jobApplicationSent.value = false
+    return
+  }
+
   await em.persist(JobApplication, jobApplication)
 
   jobApplicationSent.value = true

+ 13 - 6
components/JoinUs/MissionDetail.vue

@@ -33,7 +33,7 @@
               <v-row>
                 <div>
                   Type de contrat :
-                  <b>{{ job.contractType }} </b>
+                  <b>{{ $t(job.contractType) }} </b>
                 </div>
               </v-row>
 
@@ -46,7 +46,7 @@
             </v-col>
 
             <v-col cols="12" md="6">
-              <v-row>
+              <v-row v-if="job.sector.length > 0">
                 <div>
                   Secteur d'activité : <b>{{ job.sector.join(', ') }}</b>
                 </div>
@@ -66,18 +66,20 @@
           </v-row>
 
           <v-row class="d-flex justify-center align-center">
-            <v-btn class="btn-apply mb-12"> Je postule </v-btn>
+            <v-btn class="btn-apply mb-12" @click="apply"> Je postule </v-btn>
           </v-row>
 
+          <JoinUsDialog v-model="dialog" :job-posting-id="job.id" />
+
           <v-row class="d-flex justify-space-between center-90">
-            <p>MOTS CLÉS</p>
+            <p v-if="job.tags.length > 0">MOTS CLÉS</p>
             <div v-if="mdAndUp">
               <p>PARTAGER</p>
             </div>
           </v-row>
 
           <v-row class="d-flex justify-space-between mb-8 center-90">
-            <p class="key-word mt-3">
+            <p v-if="job.tags.length > 0" class="key-word mt-3">
               <span v-for="tag in job.tags" :key="tag.id" class="mr-2">
                 {{ tag.name }}
               </span>
@@ -115,12 +117,17 @@ 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')
 }
+
+const dialog: Ref<boolean> = ref(false)
+
+const apply = () => {
+  dialog.value = true
+}
 </script>
 
 <style scoped lang="scss">

+ 8 - 46
components/JoinUs/Missions.vue → components/JoinUs/Missions.client.vue

@@ -14,7 +14,7 @@
     >
       <div class="title-container">
         <NuxtLink :to="`/nous-rejoindre/${job.id}`" class="title">
-          {{ job.title }} - {{ job.contractType }}
+          {{ job.title }} - {{ $t(job.contractType) }}
 
           <v-icon v-if="job.featured" class="star fas fa-star" />
         </NuxtLink>
@@ -35,7 +35,11 @@
     <v-row>
       <v-col cols="12">
         <LayoutPagination
-          v-if="jobCollection && jobCollection.pagination"
+          v-if="
+            jobCollection &&
+            jobCollection.pagination &&
+            jobCollection.pagination.last! > 1
+          "
           :model-value="page"
           :pagination="jobCollection.pagination"
           class="mt-4"
@@ -62,27 +66,7 @@
     </v-row>
 
     <!-- Boite de dialogue "soumettre une candidature" -->
-    <v-dialog
-      v-model="dialog"
-      max-width="600px"
-      :persistent="!jobApplicationSent"
-      no-click-animation
-      :retain-focus="false"
-    >
-      <div v-if="!jobApplicationSent">
-        <JoinUsForm @submit="onFormSubmit" />
-
-        <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>
+    <JoinUsDialog v-model="dialog" />
   </LayoutContainer>
 </template>
 
@@ -97,7 +81,7 @@ const { fetchCollection } = useEntityFetch()
 const page: Ref<number> = ref(1)
 
 const query: ComputedRef<AnyJson> = computed(() => {
-  return { type: 'ENTREPRISE', page: page.value }
+  return { type: 'BUSINESS', page: page.value }
 })
 
 const {
@@ -130,11 +114,6 @@ 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)
-
-const onFormSubmit = () => {
-  jobApplicationSent.value = true
-}
 </script>
 
 <style scoped lang="scss">
@@ -265,21 +244,4 @@ const onFormSubmit = () => {
     margin: 12px auto;
   }
 }
-
-.v-dialog {
-  :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>

+ 12 - 2
components/Layout/AnchoredSection.vue

@@ -1,5 +1,6 @@
 <template>
-  <div :id="id" ref="section" v-scroll="onScroll">
+  <div ref="section" v-scroll="onScroll" class="section">
+    <div :id="id" class="anchor" />
     <slot />
   </div>
 </template>
@@ -48,4 +49,13 @@ const onScroll = (scroll) => {
 }
 </script>
 
-<style scoped></style>
+<style scoped lang="scss">
+.section {
+  //position: relative;
+}
+
+.anchor {
+  position: relative;
+  top: -54px;
+}
+</style>

+ 30 - 1
components/Layout/Captcha.vue

@@ -1,7 +1,11 @@
 <template>
-  <div class="d-flex flex-column">
+  <div class="d-flex flex-column container">
+    <div id="captchaBox" :class="show ? '' : 'hidden'" />
     <vue-hcaptcha
       :sitekey="siteKey"
+      challenge-container="captchaBox"
+      @opened="show = true"
+      @closed="show = false"
       @verify="onVerify"
       @expired="onExpire"
       @challenge-expired="onChallengeExpire"
@@ -22,6 +26,8 @@ import type { Ref } from 'vue'
 const runtimeConfig = useRuntimeConfig()
 const siteKey = runtimeConfig.public.hCaptchaSiteKey
 
+const show: Ref<boolean> = ref(false)
+
 const verified: Ref<boolean> = ref(false)
 const expired: Ref<boolean> = ref(false)
 const token: Ref<string> = ref('')
@@ -43,6 +49,8 @@ function onVerify(tokenStr: string, ekey: string) {
   token.value = tokenStr
   eKey.value = ekey
   emit('update', true)
+
+  show.value = false
 }
 
 function onExpire() {
@@ -78,4 +86,25 @@ function onError(err: string) {
     display: none;
   }
 }
+
+.container {
+  position: relative;
+}
+
+#captchaBox {
+  z-index: 1000;
+  position: absolute;
+  bottom: 140px;
+  box-shadow: 2px 2px 1px grey;
+  border: solid 1px lightgray;
+  background: white;
+
+  @media (min-width: 600px) {
+    left: -100px;
+  }
+}
+
+.hidden {
+  display: none;
+}
 </style>

+ 6 - 1
components/Layout/FAQ.vue

@@ -23,6 +23,7 @@
       <v-col cols="12" lg="6" class="links">
         <v-btn
           href="https://ressources.opentalent.fr/space/FAQ/2496592/Tutoriels+vid%C3%A9os"
+          aria-label="De nombreux articles tutoriels accessibles 24h/24"
           target="_blank"
         >
           <div>
@@ -35,7 +36,11 @@
           </div>
         </v-btn>
 
-        <v-btn href="https://ressources.opentalent.fr/" target="_blank">
+        <v-btn
+          href="https://ressources.opentalent.fr/"
+          target="_blank"
+          aria-label="Support accessible du lundi au vendredi via l’outil en ligne"
+        >
           <div>
             <v-img
               src="/images/components/faq/Icone_FAQ.svg"

+ 52 - 42
components/Layout/Footer/Footer.vue

@@ -15,7 +15,7 @@
           </v-col>
 
           <!-- Deuxième section : liens agenda culturel (écrans larges seulement) -->
-          <v-col v-if="lgAndUp" cols="7">
+          <v-col class="lg-and-up" cols="7">
             <v-row>
               <v-col cols="3">
                 <v-row>
@@ -25,10 +25,14 @@
                   <AgendaLink href="/annuaire"> Annuaire </AgendaLink>
                 </v-row>
                 <v-row>
-                  <AgendaLink href="/actualites"> Actualités </AgendaLink>
+                  <AgendaLink href="/actualites-culturelles">
+                    Actualités
+                  </AgendaLink>
                 </v-row>
                 <v-row>
-                  <AgendaLink href="/annonces"> Annonces </AgendaLink>
+                  <AgendaLink href="/annonces-culturelles">
+                    Annonces
+                  </AgendaLink>
                 </v-row>
               </v-col>
 
@@ -38,17 +42,17 @@
                   <h5>Logiciels</h5>
                 </v-row>
                 <v-row>
-                  <nuxt-link to="/opentalent_artist">
+                  <nuxt-link to="/opentalent-artist">
                     Opentalent Artist
                   </nuxt-link>
                 </v-row>
                 <v-row>
-                  <nuxt-link to="/opentalent_school">
+                  <nuxt-link to="/opentalent-school">
                     Opentalent School
                   </nuxt-link>
                 </v-row>
                 <v-row>
-                  <nuxt-link to="/opentalent_manager">
+                  <nuxt-link to="/opentalent-manager">
                     Opentalent Manager
                   </nuxt-link>
                 </v-row>
@@ -57,7 +61,7 @@
               <!-- Quatrième section : A propos (écrans larges seulement) -->
               <v-col cols="3">
                 <v-row>
-                  <h5>A PROPOS</h5>
+                  <h5>&Agrave; PROPOS</h5>
                 </v-row>
                 <v-row>
                   <nuxt-link to="/qui-sommes-nous"> Qui sommes-nous </nuxt-link>
@@ -96,7 +100,7 @@
           </v-col>
 
           <!-- Sixième section : liens réseaux sociaux (écrans larges seulement) -->
-          <v-col v-if="lgAndUp" cols="2">
+          <v-col class="lg-and-up" cols="2">
             <v-row class="justify-center">
               <h5>Suivez-nous</h5>
             </v-row>
@@ -136,7 +140,7 @@
         </v-row>
 
         <!-- Deuxième section alt : version petits écrans -->
-        <v-row v-if="mdAndDown" class="justify-center social-networks">
+        <v-row class="md-and-down justify-center social-networks">
           <!-- TODO: voir si faisable de fusionner avec la section précédente -->
           <v-col cols="12" class="text-center">
             <nuxt-link
@@ -164,7 +168,7 @@
       </div>
 
       <!-- Troisième section alt : version petits écrans -->
-      <v-row v-if="mdAndDown">
+      <v-row class="md-and-down">
         <v-col cols="12">
           <div v-for="(item, index) in footerLinks" :key="index">
             <v-container>
@@ -187,9 +191,9 @@
                   :key="sublinkIndex"
                   class="mt-3"
                 >
-                  <CommonAgendaLink :href="sublink.link">
+                  <nuxt-link :href="sublink.link" :target="sublink.target">
                     {{ sublink.label }}
-                  </CommonAgendaLink>
+                  </nuxt-link>
                 </div>
               </div>
             </v-container>
@@ -205,62 +209,70 @@
             <nuxt-link
               to="/politique-de-confidentialite-et-protection-des-donnees-personnelles"
             >
-              Politiques de confidentialité
+              Politiques de confidentialité et cookies
             </nuxt-link>
             -
-            <nuxt-link to="/CGV"> Conditions Générales de Ventes </nuxt-link>
+            <nuxt-link to="/cgv"> Conditions Générales de Ventes </nuxt-link>
           </p>
         </v-row>
 
         <v-row class="mb-3" justify="center">
-          <p>
-            2024 © Opentalent est une marque déposée par 2iOPENservice
-          </p>
+          <p>2024 © Opentalent est une marque déposée par 2iOPENservice</p>
         </v-row>
       </div>
     </LayoutContainer>
   </footer>
 </template>
 
-<script setup>
-import { useDisplay } from 'vuetify'
+<script setup lang="ts">
 import { useLayoutStore } from '~/stores/layoutStore'
 import AgendaLink from '~/components/Common/AgendaLink.vue'
+import UrlUtils from '~/services/utils/urlUtils.js'
+
+const runtimeConfig = useRuntimeConfig()
 
-const { mdAndDown, lgAndUp } = useDisplay()
+const makeAgendaLink = (path: string) => {
+  return UrlUtils.join(runtimeConfig.public.agendaBaseUrl as string, path)
+}
 
 const footerLinks = ref([
   {
-    label: 'AGENDA CULTUREL',
+    label: 'AGENDA',
     sublink: [
       {
         label: 'Annuaire',
-        link: '/annuaire',
+        link: makeAgendaLink('/annuaire'),
+        target: '_blank',
       },
       {
         label: 'Actualités',
-        link: '/actualites',
+        link: makeAgendaLink('/actualites-culturelles'),
+        target: '_blank',
       },
       {
         label: 'Annonces',
-        link: '/annonces',
+        link: makeAgendaLink('/annonces-culturelles'),
+        target: '_blank',
       },
     ],
   },
   {
-    label: 'LOGICIELS CULTURELS ',
+    label: 'LOGICIELS ',
     sublink: [
       {
         label: 'Opentalent Artist',
-        link: '/opentalent_artist',
+        link: '/opentalent-artist',
+        target: '_self',
       },
       {
         label: 'Opentalent School',
-        link: '/opentalent_school',
+        link: '/opentalent-school',
+        target: '_self',
       },
       {
         label: 'Opentalent Manager',
-        link: '/opentalent_manager',
+        link: '/opentalent-manager',
+        target: '_self',
       },
     ],
   },
@@ -270,14 +282,17 @@ const footerLinks = ref([
       {
         label: 'Qui sommes-nous',
         link: '/qui-sommes-nous',
+        target: '_self',
       },
       {
         label: 'Nous rejoindre',
         link: '/nous-rejoindre',
+        target: '_self',
       },
       {
         label: 'Nous contacter',
         link: '/nous-contacter',
+        target: '_self',
       },
     ],
   },
@@ -286,31 +301,29 @@ const footerLinks = ref([
     sublink: [
       {
         label: 'Foire Aux Questions ',
-        link: '/ https://ressources.opentalent.fr',
+        link: 'https://ressources.opentalent.fr',
+        target: '_self',
       },
       {
         label: 'Support en ligne ',
-        link: '/https://ressources.opentalent.fr/?contact',
-      },
-      {
-        label: 'Nous contacter ',
-        link: '/nous-contacter',
+        link: 'https://ressources.opentalent.fr/?contact',
+        target: '_self',
       },
     ],
   },
 ])
 
 const activeIndex = ref(-1)
-function toggleSection(index) {
+function toggleSection(index: number) {
   activeIndex.value = activeIndex.value === index ? -1 : index
 }
 
-function isActive(index) {
+function isActive(index: number) {
   return activeIndex.value === index
 }
 
 const layoutStore = useLayoutStore()
-const onIntersect = (isIntersecting) => {
+const onIntersect = (isIntersecting: boolean) => {
   layoutStore.setIsFooterVisible(isIntersecting)
 }
 </script>
@@ -319,6 +332,7 @@ const onIntersect = (isIntersecting) => {
 .container {
   background: var(--primary-color);
   color: var(--on-primary-color);
+  padding-top: 32px;
 }
 
 .logo {
@@ -337,10 +351,6 @@ const onIntersect = (isIntersecting) => {
   padding: 12px;
 }
 
-.container {
-  padding-top: 32px;
-}
-
 h5 {
   font-weight: 500;
   line-height: 20px;
@@ -406,7 +416,7 @@ a {
 
   .social-networks {
     a {
-      font-size: 3rem;
+      font-size: 2rem;
       margin: 0 1rem;
     }
   }

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

@@ -8,7 +8,7 @@ Première section du footer (galerie des logiciels)
     <LayoutContainer v-if="lgAndUp">
       <v-row>
         <v-col cols="4" class="text-center">
-          <nuxt-link to="/opentalent_artist">
+          <nuxt-link to="/opentalent-artist">
             <v-img
               src="/images/logos/opentalent/Logo_Opentalent_Artist-gris.png"
               alt="Logo Opentalent Artist - logiciel de gestion et de communication pour les orchestres, les chorales, les compagnies artistiques et troupes"
@@ -16,14 +16,14 @@ Première section du footer (galerie des logiciels)
           </nuxt-link>
 
           <small class="text-logo">
-            Orchestres, chorales, danse, théâtre, cirque
+            Orchestres, chorales, compagnies et troupes
           </small>
         </v-col>
 
         <v-divider :vertical="true" />
 
         <v-col cols="4" class="text-center">
-          <nuxt-link to="/opentalent_school">
+          <nuxt-link to="/opentalent-school">
             <v-img
               src="/images/logos/opentalent/Logo_Opentalent_School-gris.png"
               alt="Logo Opentalent School - logiciel de gestion et de communication pour les établissements d’enseignement artistique "
@@ -31,14 +31,14 @@ Première section du footer (galerie des logiciels)
           </nuxt-link>
 
           <small class="text-logo">
-            Tous les établissements d'enseignement artistique
+            Établissements d'enseignement artistique
           </small>
         </v-col>
 
         <v-divider :vertical="true" />
 
         <v-col cols="3" class="text-center">
-          <nuxt-link to="/opentalent_manager">
+          <nuxt-link to="/opentalent-manager">
             <v-img
               src="/images/logos/opentalent/Logo_Opentalent_Manager-gris.png"
               alt="Logo Opentalent Manager - logiciel de gestion et de communication pour les fédérations, les confédérations et les collectivités"
@@ -46,7 +46,7 @@ Première section du footer (galerie des logiciels)
           </nuxt-link>
 
           <small class="text-logo">
-            Fédérations, confédérations, collectivités
+            Fédérations, confédérations et collectivités
           </small>
         </v-col>
       </v-row>
@@ -56,7 +56,7 @@ Première section du footer (galerie des logiciels)
     <LayoutContainer v-else>
       <v-row class="justify-center">
         <v-col cols="3" class="border-right">
-          <nuxt-link to="/opentalent_artist">
+          <nuxt-link to="/opentalent-artist">
             <v-img
               src="/images/logos/opentalent/Logo_Opentalent_Artist_Griffe.png"
             />
@@ -68,7 +68,7 @@ Première section du footer (galerie des logiciels)
         </v-col>
 
         <v-col cols="3">
-          <nuxt-link to="/opentalent_school">
+          <nuxt-link to="/opentalent-school">
             <v-img
               src="/images/logos/opentalent/Logo_Opentalent_School_Griffe.png"
             />
@@ -80,7 +80,7 @@ Première section du footer (galerie des logiciels)
         </v-col>
 
         <v-col cols="3">
-          <nuxt-link to="/opentalent_manager">
+          <nuxt-link to="/opentalent-manager">
             <v-img
               src="/images/logos/opentalent/Logo_Opentalent_Manager_Griffe.png"
             />

+ 5 - 10
components/Layout/Navigation.vue

@@ -4,31 +4,28 @@ Menu Navigation
 <template>
   <div v-intersect="onIntersect">
     <!-- Navigation (écran large) -->
-    <div v-if="lgAndUp">
+    <div class="lg-and-up">
       <LayoutNavigationLg :menu="menu" />
     </div>
 
     <!-- Navigation (petit écran) -->
-    <div v-else>
+    <div class="md-and-down">
       <LayoutNavigationMd :menu="menu" />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { useDisplay } from 'vuetify'
 import type { MainMenuItem } from '~/types/interface'
 import { useLayoutStore } from '~/stores/layoutStore'
 
-const { lgAndUp } = useDisplay()
-
 const menu: Array<MainMenuItem> = [
   {
     label: 'Nos logiciels',
     children: [
-      { label: 'Opentalent Artist', to: '/opentalent_artist' },
-      { label: 'Opentalent School', to: '/opentalent_school' },
-      { label: 'Opentalent Manager', to: '/opentalent_manager' },
+      { label: 'Opentalent Artist', to: '/opentalent-artist' },
+      { label: 'Opentalent School', to: '/opentalent-school' },
+      { label: 'Opentalent Manager', to: '/opentalent-manager' },
     ],
   },
   {
@@ -54,5 +51,3 @@ const onIntersect = (isIntersecting: boolean) => {
   layoutStore.setIsHeaderVisible(isIntersecting)
 }
 </script>
-
-<style scoped></style>

+ 2 - 0
components/Layout/Navigation/Lg.vue

@@ -23,6 +23,7 @@
               v-bind="props"
               class="menuItem first-level"
               :to="item.to"
+              role="link"
             >
               {{ item.label }}
             </nuxt-link>
@@ -78,6 +79,7 @@ defineProps({
 }
 
 .navigation-lg {
+  margin-top: 0 !important;
   display: flex;
   align-items: center;
   background-color: var(--neutral-color);

+ 14 - 13
components/Layout/Navigation/Md.vue

@@ -21,13 +21,22 @@
             href="https://admin.opentalent.fr/#/login/"
             icon="fas fa-user"
             class="icon"
+            aria-label="Se connecter"
           />
 
-          <v-btn to="/nous-contacter" icon="fas fa-phone" class="icon" />
+          <v-btn
+            to="/nous-contacter"
+            icon="fas fa-phone"
+            aria-label="Nous contacter"
+            class="icon"
+          />
 
-          <AgendaLink href="/agenda-culturel">
-            <v-btn icon="fas fa-calendar" class="icon" />
-          </AgendaLink>
+          <v-btn
+            :href="runtimeConfig.public.agendaBaseUrl"
+            icon="fas fa-calendar"
+            aria-label="L'agenda culturel"
+            class="icon"
+          />
         </template>
       </v-app-bar>
 
@@ -68,11 +77,9 @@
 
 <script setup lang="ts">
 import type { PropType, Ref } from 'vue'
-import { useDisplay } from 'vuetify'
-import AgendaLink from '~/components/Common/AgendaLink.vue'
 import type { MainMenuItem } from '~/types/interface'
 
-const { mdAndDown } = useDisplay()
+const runtimeConfig = useRuntimeConfig()
 
 const props = defineProps({
   menu: {
@@ -134,12 +141,6 @@ const withAnimation = (callback: () => void) => {
     isMenuOpen.value = true
   }, 85)
 }
-
-const showBackToTheTop = ref(false)
-
-const handleScroll = () => {
-  showBackToTheTop.value = window.scrollY > 50
-}
 </script>
 
 <style scoped lang="scss">

+ 3 - 1
components/Layout/Navigation/Topbar.vue

@@ -34,7 +34,9 @@ import AgendaLink from '~/components/Common/AgendaLink.vue'
   .v-btn {
     margin: 4px 8px;
     border-radius: 6px;
-    height: 36px;
+    height: 44px;
+    font-size: 0.8rem;
+    font-weight: 400;
   }
 }
 

+ 3 - 3
components/Layout/UI/TitlePage.vue

@@ -1,5 +1,5 @@
 <template>
-  <div :class="'mb-4' + mdAndDown ? ' mt-12' : ''">
+  <div :class="'mb-4' + mdAndDown ? ' mt-8' : ''">
     <h1>
       <slot />
     </h1>
@@ -31,9 +31,9 @@ h1 {
 }
 
 h2 {
-  font-size: 1.5rem;
+  font-size: 1.8rem;
   line-height: 2rem;
-  letter-spacing: 0.2rem;
+  letter-spacing: 0.4rem;
   margin-left: 1rem;
   margin-right: 1rem;
 }

+ 4 - 4
components/Logiciels/Artist/Abonnement.vue

@@ -26,7 +26,7 @@
 
           <p class="cmf">
             Adhérents CMF ? <br />
-            Et si on vous disait que vous l’aviez déjà&nbsp;...
+            Et si on vous disait que vous l’avez déjà&nbsp;...
           </p>
 
           <div class="adherent-warning">
@@ -48,7 +48,7 @@
                 France (CMF), vous bénéficiez gratuitement, dans le cadre de
                 votre adhésion, de la version Opentalent Artist Standard, et de
                 conditions privilégiées pour la version Artist Premium.
-                Contactez nous ou contactez votre fédération pour obtenir vos
+                Contactez-nous ou contactez votre fédération pour obtenir vos
                 codes d'accès.
               </p>
 
@@ -58,7 +58,7 @@
                   target="_blank"
                   class="btn-contact"
                 >
-                  Obtenir mon code d'accès
+                  Obtenir mes identifiants
                 </v-btn>
               </div>
             </div>
@@ -73,7 +73,7 @@
 import { useDisplay } from 'vuetify'
 import AnchoredSection from '~/components/Layout/AnchoredSection.vue'
 
-const { mdAndDown, mdAndUp, lgAndUp } = useDisplay()
+const { mdAndDown, mdAndUp } = useDisplay()
 </script>
 
 <style scoped lang="scss">

+ 1 - 3
components/Logiciels/Artist/Abonnement/ToSubscribe.vue

@@ -23,9 +23,7 @@
   <div class="subscription-steps">
     <ol>
       <li class="mt-6">Téléchargez et complétez le formulaire</li>
-      <li>
-        Réglez votre abonnement par virement ou par chèque
-      </li>
+      <li>Réglez votre abonnement par virement ou par chèque</li>
       <li>
         Après réception de votre formulaire d'adhésion et de votre règlement,
         nous vous ouvrons le service choisi. Vous recevrez alors un mail avec

+ 18 - 18
components/Logiciels/Artist/Fonctionnalites.vue

@@ -18,8 +18,8 @@ const cards: Array<Functionality> = [
     logo: '/images/components/fonctionnalites/Icone_espaces_dedies.svg',
     logoAlt: 'Icône smartphone avec cadenas verrouillé',
     title: 'ESPACES DÉDIÉS *',
-    list: ['Administrations', 'Membres/Adhérents'],
-    options: ['*Disponible sur tous supports'],
+    list: ['Administration', 'Membres/Adhérents'],
+    options: ['*Disponible sur tout support'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_repertoire.svg',
@@ -60,17 +60,7 @@ const cards: Array<Functionality> = [
       'Création de modèles de courriers, mails ou SMS **',
       'Outil de publipostage intégré pour un envoi personnalisé',
     ],
-    options: ['* version Artist Premium ', '** en option'],
-  },
-  {
-    logo: '/images/components/fonctionnalites/Icone_site_internet.svg',
-    logoAlt: 'Icône site internet',
-    title: 'SITE INTERNET ',
-    list: [
-      'Gestion intégrée au logiciel',
-      'Mise à jour automatique des membres et événements sur votre site',
-      'Possibilité de personnalisé votre template',
-    ],
+    options: ['* Version Artist Premium ', '** En option'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_statistiques.svg',
@@ -83,15 +73,25 @@ const cards: Array<Functionality> = [
     ],
   },
   {
-    logo: '/images/logos/cmf/Icone_CMF_reseau_BLACK.png',
+    logo: '/images/components/fonctionnalites/Icone_site_internet.svg',
+    logoAlt: 'Icône site internet',
+    title: 'SITE INTERNET ',
+    list: [
+      'Gestion intégrée au logiciel',
+      'Mise à jour automatique des membres et événements sur votre site',
+      'Possibilité de personnalisé votre template',
+    ],
+  },
+  {
+    logo: '/images/components/fonctionnalites/Icone_communication_en_reseau.svg',
     logoAlt: 'Icône du logo de la CMF (confédération musicale de France)',
-    title: 'RÉSEAU CMF *',
+    title: 'FONCTIONNEMENT EN RÉSEAU *',
     list: [
       'Accès au répertoire du réseau',
       'Renouvellement de votre adhésion fédérale',
-      "Gestion de l'assurance CMF",
+      'Fonctionnalités spécifiques sur devis',
     ],
-    options: ['* uniquement dédié aux adhérents CMF'],
+    options: ['* avec notre solution Opentalent Manager'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_promotion.svg',
@@ -99,7 +99,7 @@ const cards: Array<Functionality> = [
     title: 'PROMOTION DE VOTRE STRUCTURE & VOS ÉVÉNEMENTS ',
     list: [
       'Sur votre site internet intégré',
-      "Sur l'agenda de la CMF",
+      'Sur les sites de votre réseau',
       "Sur l'agenda culturel Opentalent ",
     ],
   },

+ 8 - 11
components/Logiciels/Artist/Formations.vue

@@ -10,7 +10,7 @@
           </v-row>
 
           <v-row class="formation center-90 py-12 align-center mb-12">
-            <v-col cols="12" lg="4">
+            <v-col cols="12" lg="6">
               <v-img
                 src="/images/pages/opentalent_artist/formations/Webinaire_Opentalent_Artist.jpg"
                 alt="Ordinateur avec un écran en visioconférence avec plusieurs personnes posé sur une table de salon devant une fenêtre avec des plantes et une tasse posées à côté"
@@ -24,18 +24,18 @@
 
               <p class="details">
                 Rejoignez notre webinaire, spécialement conçu pour les
-                professionnels du secteur culturel, orchestres, chorales,
-                compagnies de danse, ainsi que les troupes de théâtre et de
-                cirque. Cette session interactive vous offre une occasion unique
-                de vous immerger dans les fonctionnalités de notre logiciel, de
+                professionnels du secteur culturel : orchestres, chorales,
+                compagnies de danse, troupes de théâtre et de cirque. Cette
+                session interactive vous offre une occasion unique de vous
+                immerger dans les fonctionnalités de notre logiciel, de
                 comprendre ses avantages distinctifs et d'explorer les diverses
-                versions disponibles. Ne manquez pas cette chance de simplifiez
+                versions disponibles. Ne manquez pas cette chance de simplifier
                 votre gestion et de faire évoluer votre pratique artistique avec
                 nos solutions technologiques innovantes !
               </p>
 
               <nuxt-link to="/webinaires">
-                <v-btn> S'inscrire à nos webinaires </v-btn>
+                <v-btn> S'inscrire aux webinaires </v-btn>
               </nuxt-link>
             </v-col>
           </v-row>
@@ -65,10 +65,7 @@ import AnchoredSection from '~/components/Layout/AnchoredSection.vue'
     background-position: center;
     background-size: cover;
     border-radius: 10%;
-
-    @media (max-width: 1240px) {
-      margin: 0 auto;
-    }
+    margin: 0 auto;
   }
 
   :deep(.v-img img) {

+ 1 - 1
components/Logiciels/Artist/SomeNumbers.vue

@@ -15,7 +15,7 @@
         </v-col>
 
         <v-col lg="3" class="d-flex justify-center align-center">
-          <CommonCardStat number="15" text="Années d'expérience" />
+          <CommonCardStat number="17" text="Années d'expérience" />
         </v-col>
       </v-row>
     </v-container>

+ 5 - 5
components/Logiciels/Manager/Avantages.vue

@@ -16,18 +16,18 @@ const benefits: Array<Benefit> = [
     title: 'Sur-mesure',
     number: '01',
     description:
-      "S'adapte à tous les réseaux de type pyramidal, quelque soit le nombre de niveaux : fédérations, institutions publiques...",
+      "S'adapte à tous les réseaux de type pyramidal, quel que soit le nombre de niveaux : fédérations, institutions publiques...",
     image:
       '/images/pages/opentalent_manager/avantages/Un_logiciel_sur-mesure.jpg',
     alt: 'Mètre ruban de couture',
   },
   {
-    title: 'Adapté',
+    title: 'Un logiciel pour chaque structure',
     number: '02',
     description:
       'Chaque structure du réseau dispose de sa propre solution indépendante et connectée au réseau : Opentalent manager, Opentalent school, Opentalent artist',
     image:
-      '/images/pages/opentalent_manager/avantages/Un_logiciel_adapte_a_chaque_reseau.jpg',
+      '/images/pages/opentalent_manager/avantages/Un_logiciel_adapte_a_chaque_reseau.png',
     alt: 'Réseaux structurés différemment ayant un lien part un atome commun',
   },
   {
@@ -48,10 +48,10 @@ const benefits: Array<Benefit> = [
     alt: 'Commandant de bord posant en uniforme',
   },
   {
-    title: 'En réseau',
+    title: 'Automatisation de tâches',
     number: '05',
     description:
-      'Mise à jour automatique des coordonnées publiques entre les membres du réseau.',
+      'Partage des informations publiques de chaque structure : coordonnées des structures et des responsables, événements publics.',
     image:
       '/images/pages/opentalent_manager/avantages/Un_logiciel_adapte_a_chaque_reseau.jpg',
     alt: 'Multi-réseau',

+ 1 - 1
components/Logiciels/Manager/Fonctionnalites.vue

@@ -23,7 +23,7 @@ const cards: Array<Functionality> = [
     logoAlt: 'Icône smartphone avec cadenas verrouillé',
     title: 'ESPACES DÉDIÉS *',
     list: ['Administration', 'Membres / Adhérents'],
-    options: ['*Disponible sur tous supports '],
+    options: ['*Disponible sur tout support '],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_repertoire.svg',

+ 1 - 1
components/Logiciels/Manager/Presentation.vue

@@ -57,7 +57,7 @@ const pictos: Array<FeaturePicto> = [
   // },
   {
     src: '/images/pages/opentalent_manager/presentation/picto6.png',
-    text: 'Pout tout type de réseau pyramidal',
+    text: 'Pour tout type de réseau pyramidal',
   },
 ]
 </script>

+ 1 - 1
components/Logiciels/Manager/SomeNumbers.vue

@@ -21,7 +21,7 @@
     <v-row>
       <v-col cols="12" class="justify-center">
         <span class="cmf-trust-statement">
-          La plus grande Confédération Musicale de France nous fait confiance
+          Le plus grand réseau culturel en France nous fait confiance
         </span>
 
         <nuxt-link href="https://www.cmf-musique.org/" target="_blank">

+ 43 - 16
components/Logiciels/School/Comparatif.vue

@@ -13,6 +13,10 @@
           premium-price="49€"
           :items="comparisonItems"
         />
+
+        <div class="asterisk">
+          <span>** Uniquement dédié aux structures adhérentes CMF</span>
+        </div>
       </v-row>
     </LayoutContainer>
   </AnchoredSection>
@@ -29,34 +33,34 @@ const comparisonItems: Array<ComparisonItem> = [
     includedInPremium: true,
   },
   {
-    label: 'AGENDA',
+    label: 'SUIVI PÉDAGOGIQUE',
     includedInStandard: true,
     includedInPremium: true,
   },
   {
-    label: 'SUIVI PÉDAGOGIQUE',
+    label: 'AGENDA',
     includedInStandard: true,
     includedInPremium: true,
   },
   {
-    label: 'GESTION DU PARC MATÉRIEL',
+    label: 'FACTURATION AUTOMATISÉE',
     includedInStandard: true,
     includedInPremium: true,
   },
   {
-    label: 'COMMUNICATION',
+    label: 'GESTION DES RÈGLEMENTS',
     includedInStandard: true,
     includedInPremium: true,
   },
   {
-    label: 'SMS',
-    includedInStandard: 'Option',
-    includedInPremium: 'Option',
+    label: 'GESTION DU PARC MATÉRIEL',
+    includedInStandard: true,
+    includedInPremium: true,
   },
   {
-    label: 'NOM DE DOMAINE',
-    includedInStandard: 'Option',
-    includedInPremium: 'Option',
+    label: 'COMMUNICATION',
+    includedInStandard: true,
+    includedInPremium: true,
   },
   {
     label: 'SITE INTERNET',
@@ -69,19 +73,19 @@ const comparisonItems: Array<ComparisonItem> = [
     includedInPremium: true,
   },
   {
-    label: 'FONCTIONNALITÉ DU RÉSEAU CMF',
+    label: 'EXPORT DES DONNÉES',
     includedInStandard: true,
     includedInPremium: true,
   },
   {
-    label: 'SAUVEGARDE',
+    label: 'FONCTIONNALITÉ DU RÉSEAU CMF **',
     includedInStandard: true,
     includedInPremium: true,
   },
   {
     label: 'EXTRANET UTILISATEURS',
     includedInStandard: false,
-    includedInPremium: 'Option',
+    includedInPremium: true,
   },
   {
     label: 'PRÉINSCRIPTION EN LIGNE',
@@ -90,11 +94,26 @@ const comparisonItems: Array<ComparisonItem> = [
   },
   {
     label: "GRILLES D'ÉVALUATION",
-    includedInStandard: 'Option',
+    includedInStandard: false,
     includedInPremium: 'Option',
   },
   {
     label: 'GESTION DES RÈGLEMENTS',
+    includedInStandard: false,
+    includedInPremium: 'Option',
+  },
+  {
+    label: 'EXPORT DE FACTURATION',
+    includedInStandard: false,
+    includedInPremium: 'Option',
+  },
+  {
+    label: 'SMS',
+    includedInStandard: 'Option',
+    includedInPremium: 'Option',
+  },
+  {
+    label: 'NOM DE DOMAINE',
     includedInStandard: 'Option',
     includedInPremium: 'Option',
   },
@@ -104,10 +123,18 @@ const comparisonItems: Array<ComparisonItem> = [
     includedInPremium: '1 Go',
   },
   {
-    label: 'PAGE DU SITE INTERNET',
+    label: 'PAGES DU SITE INTERNET',
     includedInStandard: 'Restreint',
     includedInPremium: 'Illimité',
   },
 ]
 </script>
-<style scoped></style>
+<style scoped>
+.asterisk {
+  display: flex;
+  margin: 4px auto 0 auto;
+  width: 63%;
+  justify-content: flex-end;
+  font-style: italic;
+}
+</style>

+ 9 - 9
components/Logiciels/School/Fonctionnalites.vue

@@ -17,14 +17,14 @@ const cards: Array<Functionality> = [
     logoAlt: 'Icône smartphone avec cadenas verrouillé',
     title: 'ESPACES DÉDIÉS *',
     list: ['Administration', 'Professeurs', 'Élèves / Familles'],
-    options: ['* Disponible sur tous supports'],
+    options: ['* Disponible sur tout support'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_repertoire.svg',
     logoAlt: 'Icône carnet annuaire',
     title: 'RÉPERTOIRE',
     list: [
-      'Élèves et responsable légaux',
+      'Élèves et responsables légaux',
       'Personnel administratif et professeurs',
       'Contacts extérieurs, partenaires & donateurs',
     ],
@@ -38,7 +38,7 @@ const cards: Array<Functionality> = [
       'Gestion des réinscriptions et des nouvelles inscriptions',
       'Gestion des quotas et du suivi des préinscriptions en ligne',
     ],
-    options: ['* en option'],
+    options: ['* En option'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_agenda.svg',
@@ -47,7 +47,7 @@ const cards: Array<Functionality> = [
     list: [
       'Création et gestion des cours, examens, événements et prestations pédagogiques',
       'Planning interactif avec un contrôle de cohérence',
-      'Gestion des présences et absences',
+      'Gestion des présences, absences et retards',
     ],
   },
   {
@@ -56,8 +56,8 @@ const cards: Array<Functionality> = [
     title: 'PARC MATÉRIEL ',
     list: [
       'Gestion de votre parc matériel (instruments, costumes, accessoires..)',
-      'Planning interactif avec un contrôle de cohérence',
-      'Gestion des présences et absences',
+      'Locations et prêts de matériel',
+      'Planification de la maintenance et entretien de matériel',
     ],
   },
   {
@@ -87,10 +87,10 @@ const cards: Array<Functionality> = [
     title: 'COMMUNICATION',
     list: [
       'Édition et envoi de courriers, de mails ou de SMS*',
-      'Création de modèles de courriers, mails ou SMS',
+      'Création de modèles de courriers, mails ou SMS*',
       'Outil de publipostage intégré pour un envoi personnalisé',
     ],
-    options: ['* en option'],
+    options: ['* En option'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_site_internet.svg',
@@ -121,7 +121,7 @@ const cards: Array<Functionality> = [
       'Renouvellement de votre adhésion fédérale',
       "Gestion de l'assurance CMF",
     ],
-    options: ['* Uniquement dédié au adhérents CMF'],
+    options: ['* Uniquement dédié aux adhérents CMF'],
   },
   {
     logo: '/images/components/fonctionnalites/Icone_promotion.svg',

+ 3 - 3
components/Logiciels/School/Reviews.vue

@@ -24,21 +24,21 @@ const cards: Array<Review> = [
   },
   {
     review:
-      "Étant présente depuis presque le début, je suis fière d'avoir vu grandir ce logiciel et d'avoir évoluée avec lui. De plus, je me suis sentie écoutée lors de mes propositions d'amélioration, car beaucoup ont vu le jour. Enfin, l'accompagnement et la réactivité n'ont jamais faibli depuis toutes ces années",
+      "Étant présente depuis presque le début, je suis fière d'avoir vu grandir ce logiciel et d'avoir évoluée avec lui. De plus, je me suis sentie écoutée lors de mes propositions d'amélioration, car beaucoup ont vu le jour. Enfin, l'accompagnement et la réactivité n'ont jamais faibli depuis toutes ces années.",
     name: 'Karine GIRAUD',
     status: 'Secrétaire administrative',
     structure: 'Association Musicale Sainte Cécile de Lagord (17)',
   },
   {
     review:
-      "Logiciel très complet qui permet de faire beaucoup de choses. J’apprécie particulièrement la réactivité, la bienveillance et le fait que l’équipe soit à l'écoute pour faire évoluer l'outil en fonction de nos besoins. Si besoin, la FAQ est vraiment utile. Elle permet de trouver rapidement une solution face à un problème rencontré..",
+      "Logiciel très complet qui permet de faire beaucoup de choses. J’apprécie particulièrement la réactivité, la bienveillance et le fait que l’équipe soit à l'écoute pour faire évoluer l'outil en fonction de nos besoins. Si besoin, la FAQ est vraiment utile. Elle permet de trouver rapidement une solution face à un problème rencontré.",
     name: 'Laurent BEL',
     status: 'Directeur administratif & pédagogique',
     structure: ' École de Musique EPIC Musique en 4 Rivières (74)',
   },
   {
     review:
-      "Opentalent est une entreprise avec de vraies valeurs humaines, à l'écoute de chaque structure et qui ne cesse de s'améliorer pour toujours coller aux besoins de ses clients. Plus qu'une relation commerciale, c'est pour nous un véritable partenaire au quotidien..",
+      "Opentalent est une entreprise avec de vraies valeurs humaines, à l'écoute de chaque structure et qui ne cesse de s'améliorer pour toujours coller aux besoins de ses clients. Plus qu'une relation commerciale, c'est pour nous un véritable partenaire au quotidien.",
     name: 'Philippe BORY',
     status: 'Personnel administratif',
     structure: "École d'Arts de Saint-Michel-sur-Orge (91)",

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

@@ -23,20 +23,19 @@
         <CommonCardStat number="30 > 1 500" text="Élèves" />
       </v-col>
       <v-col lg="3" md="6" cols="12" class="d-flex justify-center align-center">
-        <CommonCardStat number="139" text="Clients" />
+        <CommonCardStat number="Plus de 200" text="Clients" />
       </v-col>
       <v-col lg="3" md="6" cols="12" class="d-flex justify-center align-center">
-        <CommonCardStat number="153 602" text="Utilisateurs" />
+        <CommonCardStat number="Plus de 160 000" text="Utilisateurs" />
       </v-col>
       <v-col lg="3" md="6" cols="12" class="d-flex justify-center align-center">
-        <CommonCardStat number="17" text="Années d'expérience" />
+        <CommonCardStat number="15" text="Années d'expérience" />
       </v-col>
     </v-row>
 
     <CommonCarouselClients :items="items">
       <template #title>
-        <span class="alt-color">139 structures</span> nous font
-        confiance
+        <span class="alt-color">139 structures</span> nous font confiance
       </template>
     </CommonCarouselClients>
   </LayoutContainer>

+ 25 - 0
components/News/Details.vue

@@ -40,6 +40,10 @@
               <strong>
                 {{ newsItem.leadText }}
               </strong>
+
+              <p>
+                {{ formattedPublicationDate }}
+              </p>
             </v-col>
           </v-row>
 
@@ -90,6 +94,8 @@
 
 <script setup lang="ts">
 import { useDisplay } from 'vuetify'
+import { parseISO, format } from 'date-fns'
+import { fr } from 'date-fns/locale'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import News from '~/models/Maestro/News'
 
@@ -112,6 +118,25 @@ const getImageUrl = (attachment: string): string | null => {
   }
   return `${config.public.apiBaseUrl}/uploads/news/${attachment}`
 }
+
+const formattedPublicationDate = computed(() => {
+  if (pending.value || !newsItem.value) {
+    return ''
+  }
+
+  const date = parseISO(newsItem.value.startPublication)
+
+  let formattedPublicationDate = format(date, "'Le' dd MMMM yyyy", {
+    locale: fr,
+  })
+
+  formattedPublicationDate = formattedPublicationDate.replace(
+    'Le 01 ',
+    'Le 1er '
+  )
+
+  return formattedPublicationDate
+})
 </script>
 
 <style scoped>

+ 0 - 0
components/News/List.vue → components/News/List.client.vue


+ 133 - 116
components/Webinaire/Catalogue.vue

@@ -1,85 +1,94 @@
 <template>
   <LayoutContainer>
     <div>
-      <v-row class="center-90">
-        <LayoutUISubTitle> Des webinaires pour tous </LayoutUISubTitle>
-      </v-row>
-
-      <v-row class="center-90">
-        <v-col cols="12" class="section-title">
-          <h3>Simplifiez la gestion et la communication de votre structure</h3>
-
-          <div class="strong-label">
-            Votre structure culturelle, établissement d’enseignement artistique
-            ou fédération mérite les outils les plus performants du marché pour
-            briller en toute simplicité. Découvrez comment nos outils peuvent
-            transformer votre quotidien :
-          </div>
-        </v-col>
-      </v-row>
-
-      <v-row class="center-90 catalog">
-        <v-col v-for="(course, index) in courses" :key="index" cols="12" md="4">
-          <v-card class="mb-4">
-            <v-card-text>
-              <div class="title-card-container">
-                <v-img :src="course.imageUrl" :alt="course.imageAlt" />
-
-                <h4>
-                  {{ course.title }}
-                </h4>
-              </div>
-
-              <p class="details-card">
-                {{ course.description }}
-              </p>
-
-              <div class="objectives mt-6">
-                <h6>Objectifs</h6>
-
-                <ul>
-                  <li
-                    v-for="(objective, objIndex) in course.objectives"
-                    :key="objIndex"
-                  >
-                    {{ objective }}
-                  </li>
-                </ul>
-              </div>
-
-              <div class="badge-time">Durée : {{ course.duration }}</div>
-
-              <div class="program">
-                <h6>Programme</h6>
-
-                <v-row>
-                  <v-col
-                    v-for="column in course.additionalObjectives"
-                    :key="column.id"
-                    cols="6"
-                  >
-                    <ul>
-                      <li
-                        v-for="(objective, objIndex) in column.objectives"
-                        :key="objIndex"
-                      >
-                        {{ objective }}
-                      </li>
-                    </ul>
-                  </v-col>
-                </v-row>
-              </div>
-
-              <div class="badge-time">
-                {{ course.price }}
-              </div>
-
-              <v-chip class="chip-register" @click="showModal(course.title)">
-                Inscrivez-vous
-              </v-chip>
-            </v-card-text>
-          </v-card>
-        </v-col>
+      <v-row class="background-block">
+        <v-row class="center-90">
+          <LayoutUISubTitle> Des webinaires pour tous </LayoutUISubTitle>
+        </v-row>
+
+        <v-row class="center-90">
+          <v-col cols="12" class="section-title">
+            <h3>
+              Simplifiez la gestion et la communication de votre structure
+            </h3>
+
+            <div class="strong-label">
+              Votre structure culturelle, établissement d’enseignement
+              artistique ou fédération mérite les outils les plus performants du
+              marché pour briller en toute simplicité. Découvrez comment nos
+              outils peuvent transformer votre quotidien :
+            </div>
+          </v-col>
+        </v-row>
+
+        <v-row class="center-90 catalog">
+          <v-col
+            v-for="(course, index) in courses"
+            :key="index"
+            cols="12"
+            md="4"
+          >
+            <v-card class="mb-4">
+              <v-card-text>
+                <div class="title-card-container">
+                  <v-img :src="course.imageUrl" :alt="course.imageAlt" />
+
+                  <h4>
+                    {{ course.title }}
+                  </h4>
+                </div>
+
+                <p class="details-card">
+                  {{ course.description }}
+                </p>
+
+                <div class="objectives mt-6">
+                  <h6>Objectifs</h6>
+
+                  <ul>
+                    <li
+                      v-for="(objective, objIndex) in course.objectives"
+                      :key="objIndex"
+                    >
+                      {{ objective }}
+                    </li>
+                  </ul>
+                </div>
+
+                <div class="badge-time">Durée : {{ course.duration }}</div>
+
+                <div class="program">
+                  <h6>Programme</h6>
+
+                  <v-row>
+                    <v-col
+                      v-for="column in course.additionalObjectives"
+                      :key="column.id"
+                      cols="6"
+                    >
+                      <ul>
+                        <li
+                          v-for="(objective, objIndex) in column.objectives"
+                          :key="objIndex"
+                        >
+                          {{ objective }}
+                        </li>
+                      </ul>
+                    </v-col>
+                  </v-row>
+                </div>
+
+                <div class="badge-time">
+                  {{ course.price }}
+                </div>
+
+                <v-chip class="chip-register" @click="showModal(course.title)">
+                  Inscrivez-vous
+                </v-chip>
+              </v-card-text>
+            </v-card>
+          </v-col>
+        </v-row>
       </v-row>
     </div>
 
@@ -91,31 +100,22 @@
       :scrollable="true"
       class="calendar-modal"
     >
-      <template v-slot:default="{ isActive }">
-        <div class="alt-theme d-flex flex-column align-center">
-            <v-card
-              title="Inscrivez vous"
-            >
-              <v-card-text style="height: 70vh;">
-                <h4 class="title-inscription text-center mt-4">
-                  Vous y êtes presque !
-                </h4>
-
-                <iframe
-                  :src="webinaireCalendars[selectedWebinar]"
-                  width="700"
-                  height="700"
-                />
-              </v-card-text>
-            </v-card>
-
-            <v-row>
-              <v-col cols="12">
-                <v-btn class="close-button" @click="closeModal()"> Fermer </v-btn>
-              </v-col>
-            </v-row>
-        </div>
-      </template>
+      <v-card
+        title="Inscrivez-vous"
+        class="d-flex flex-column align-center alt-theme"
+      >
+        <v-card-text>
+          <h4 class="title-inscription text-center mt-4">
+            Vous y êtes presque !
+          </h4>
+
+          <iframe :src="webinaireCalendars[selectedWebinar]" height="700" />
+        </v-card-text>
+
+        <v-card-actions>
+          <v-btn class="close-button" @click="closeModal()"> Fermer </v-btn>
+        </v-card-actions>
+      </v-card>
     </v-dialog>
   </LayoutContainer>
 </template>
@@ -128,9 +128,9 @@ const courses: Array<Training> = [
   {
     imageUrl: '/images/logos/opentalent/Logo_Opentalent_Artist_Griffe.png',
     imageAlt: 'Esperluette du logo Opentalent Artist',
-    title: 'Webinaire Artist ',
+    title: 'Webinaire Artist',
     description:
-      'Ce webinaire est destiné aux acteurs culturels tels que les orchestres, les chorales, les compagnies et troupes de danse, théâtre et cirque. Il vous permettra de découvrir les fonctionnalités du logiciels, les avantages et les différentes versions.. ',
+      'Ce webinaire est destiné aux acteurs culturels tels que les orchestres, les chorales, les compagnies et troupes de danse, théâtre et cirque. Il vous permettra de découvrir les fonctionnalités du logiciels, les avantages et les différentes versions.',
     objectives: [
       'Obtenir une présentation du logiciel Opentalent Artist',
       'Présentation des principales fonctionnalités',
@@ -205,7 +205,7 @@ const courses: Array<Training> = [
     imageUrl: '/images/logos/opentalent/Logo_Opentalent_Manager_Griffe.png',
     imageAlt: 'Esperluette du logo Opentalent Manager',
     description:
-      "Ces webinaires  sont spécialement conçues pour les utilisateurs du logiciel fédéral de la CMF (Confédération Musicale de France). Gagner en temps administratif, booster vos performances et optimiser l'utilisation du logiciel.",
+      "Ces webinaires sont spécialement conçus pour les utilisateurs du logiciel fédéral de la CMF (Confédération Musicale de France). Gagnez en temps administratif, boostez vos performances et optimisez l'utilisation du logiciel.",
     objectives: [
       "Configurer l'appel de cotisation",
       "Suivre l'appel de cotisation",
@@ -239,11 +239,11 @@ const selectedWebinar: Ref<string | null> = ref(null)
 
 const webinaireCalendars: Record<string, string> = {
   'Webinaire Artist':
-    'https://widget.weezevent.com/ticket/E920851/?code=62708&locale=fr-FR&width_auto=1&color_primary=0e2d32',
+    'https://widget.weezevent.com/ticket/E1054691/?code=1227&locale=fr-FR&width_auto=1&color_primary=00AEEF',
   'Webinaire School':
-    'https://widget.weezevent.com/ticket/E963899/?code=47365&locale=fr-FR&width_auto=1&color_primary=0e2d32',
+    'https://widget.weezevent.com/ticket/E1054694/?code=44289&locale=fr-FR&width_auto=1&color_primary=00AEEF',
   'Webinaire Manager':
-    'https://widget.weezevent.com/ticket/E923624/?code=4857&locale=fr-FR&width_auto=1&color_primary=0e2d32',
+    'https://widget.weezevent.com/ticket/E1054695/?code=76806&locale=fr-FR&width_auto=1&color_primary=00AEEF',
 }
 
 const showModal = (webinaireTitle: string) => {
@@ -270,7 +270,6 @@ const closeModal = () => {
 
   h3 {
     font-size: 42px;
-    letter-spacing: 0.1rem;
     line-height: 3.5rem;
     margin-bottom: 0.5rem;
     margin-top: 2rem;
@@ -279,16 +278,16 @@ const closeModal = () => {
   .strong-label {
     font-size: 1.5rem;
     font-weight: 400 !important;
-    letter-spacing: 0.1rem;
     line-height: 2rem;
     margin-bottom: 1rem;
   }
 }
 
-.catalog {
-  padding: 2rem;
+.background-block {
   background: var(--neutral-color-alt-light);
-
+  padding: 4rem;
+}
+.catalog {
   .title-card-container {
     display: flex;
     align-items: center;
@@ -384,10 +383,28 @@ const closeModal = () => {
   h4 {
     font-weight: 600;
     font-size: 2rem;
-    line-height: 18px;
+    line-height: 32px;
     margin-bottom: 2rem;
   }
 
+  .v-card {
+    border: none !important;
+    box-shadow: none !important;
+    background-color: var(--primary-color) !important;
+    width: 100%;
+  }
+
+  .v-card-text {
+    width: 90%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+
+    iframe {
+      width: 100%;
+    }
+  }
+
   .close-button {
     background-color: #e34461; /* TODO: pqoi cette couleur ici? */
     color: var(--on-primary-color);

+ 11 - 8
components/Webinaire/FAQ.vue

@@ -9,7 +9,7 @@ Foire aux questions
 
     <v-row class="center-90">
       <v-col cols="12" class="section-title">
-        <h3>Tout savoir sur nos webinaire en ligne</h3>
+        <h3>Tout savoir sur nos webinaires en ligne</h3>
 
         <div class="strong-label">Les questions les plus fréquentes</div>
       </v-col>
@@ -50,19 +50,22 @@ const faqItems: Array<FaqEntry> = [
   {
     question: 'Comment se passe un webinaire ?',
     answer:
-      "Inscrivez-vous à l'un de nos webinaires en choisissant une date sur l'agenda et renseignez vos informations. " +
-      "Vous recevrez à la suite de votre inscription sur notre site, un email de confirmation avec un rappel de la date et de l'horaire de participation ainsi que l'URL de connexion pour le webinaire. " +
-      'Un mail de relance à J-1 vous sera envoyer vous rappelant votre participation au webinaire. ' +
-      "Le jour J, cliquez sur l'URL de connexion. Nous vous encourageons à arriver quelques minutes en avance pour vous assurer de pouvoir accéder au webinaire sans aucun problème technique. " +
-      'Lors du début du webinaire, toutes les instructions nécessaires vous seront fournies.',
+      '<strong>Inscription et confirmation</strong>' +
+      "<ol style='margin-top:10px;margin-bottom:10px;padding-left: 20px;'><li>Inscrivez-vous à l'un de nos webinaires en choisissant une date sur l'agenda et renseignez vos informations.</li>" +
+      "<li>Vous recevrez à la suite de votre inscription sur notre site, un email de confirmation avec un rappel de la date et de l'horaire de participation ainsi que l'URL de connexion pour le webinaire.</li>" +
+      '<li>Un mail de relance à J-1 vous sera envoyer vous rappelant votre participation au webinaire.</li></ol>' +
+      '<strong>Le jour J</strong>' +
+      "<ol style='margin-top:10px;margin-bottom:10px;padding-left: 20px;'><li>Cliquez sur l'URL de connexion." +
+      '<li>Nous vous encourageons à arriver quelques minutes en avance pour vous assurer de pouvoir accéder au webinaire sans aucun problème technique.</li>' +
+      '<li>Lors du début du webinaire, toutes les instructions nécessaires vous seront fournies.</li></ol>',
   },
   {
     question: 'De quel matériel aurais-je besoin pour suivre le webinaire ?',
     answer:
-      "Pour plus de confort, il est recommandé d'être équipé d'un outil (de préférence un ordinateur) disposant d'un micro et de haut-parleur.",
+      "Pour plus de confort, il est recommandé d'être équipé d'un outil (de préférence un ordinateur) disposant d'un micro et de haut-parleurs.",
   },
   {
-    question: "Y-a-t'il une limite de participants ?",
+    question: 'Y a-t-il une limite de participants ?',
     answer:
       "Il n'y a pas de limite de participants lors de nos webinaires. Cependant, nous nous réservons le droit d'annuler une session si le nombre de participants est inférieur à 3 personnes.",
   },

+ 15 - 1
composables/useClientDevice.ts

@@ -1,7 +1,21 @@
+import { useRequestHeaders } from '#app'
+
 export function useClientDevice() {
   const isMobileDevice = () => {
+    let userAgent = navigator ? navigator.userAgent : null
+
+    if (userAgent === null) {
+      const headers = useRequestHeaders()
+      console.error(headers)
+      userAgent = headers['user-agent']
+    }
+
+    if (!userAgent) {
+      return false
+    }
+
     return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
-      navigator.userAgent
+      userAgent
     )
   }
 

+ 4 - 1
env/.env.ci

@@ -1,11 +1,14 @@
 ## LOCAL ENVIRONMENT FILE
-NUXT_ENV=dev
+NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
 NUXT_API_BASE_URL=none
 NUXT_PUBLIC_API_BASE_URL=none
 
+NUXT_SITE_URL=none
+NUXT_PUBLIC_SITE_URL=none
+
 NUXT_AGENDA_BASE_URL=none
 NUXT_PUBLIC_AGENDA_BASE_URL=none
 

+ 3 - 0
env/.env.docker

@@ -6,6 +6,9 @@ DEBUG=1
 NUXT_API_BASE_URL=https://nginx_maestro
 NUXT_PUBLIC_API_BASE_URL=https://local.maestro.opentalent.fr
 
+NUXT_SITE_URL=http://local.logiciels.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://local.logiciels.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://local.agenda.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://local.agenda.opentalent.fr
 

+ 7 - 2
env/.env.prod

@@ -3,8 +3,13 @@
 NUXT_ENV=production
 NUXT_DEBUG=0
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.opentalent.fr
 
-NUXT_AGENDA_BASE_URL=https://opentalent.fr
-NUXT_PUBLIC_AGENDA_BASE_URL=https://opentalent.fr
+NUXT_SITE_URL=http://logiciels.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.opentalent.fr
+
+NUXT_AGENDA_BASE_URL=https://new.opentalent.fr
+NUXT_PUBLIC_AGENDA_BASE_URL=https://new.opentalent.fr

+ 5 - 0
env/.env.test

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test.opentalent.fr

+ 5 - 0
env/.env.test1

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test1.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test1.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test1.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test1.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test1.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test1.opentalent.fr

+ 5 - 0
env/.env.test2

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test2.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test2.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test2.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test2.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test2.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test2.opentalent.fr

+ 5 - 0
env/.env.test3

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test3.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test3.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test3.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test3.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test3.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test3.opentalent.fr

+ 5 - 0
env/.env.test4

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test4.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test4.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test4.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test4.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test4.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test4.opentalent.fr

+ 5 - 0
env/.env.test5

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test5.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test5.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test5.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test5.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test5.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test5.opentalent.fr

+ 5 - 0
env/.env.test6

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test6.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test6.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test6.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test6.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test6.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test6.opentalent.fr

+ 5 - 0
env/.env.test7

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test7.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test7.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test7.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test7.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test7.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test7.opentalent.fr

+ 5 - 0
env/.env.test8

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test8.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test8.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test8.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test8.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test8.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test8.opentalent.fr

+ 5 - 0
env/.env.test9

@@ -2,8 +2,13 @@ NUXT_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 
+PORT=3003
+
 NUXT_API_BASE_URL=https://maestro.test9.opentalent.fr
 NUXT_PUBLIC_API_BASE_URL=https://maestro.test9.opentalent.fr
 
+NUXT_SITE_URL=http://logiciels.test9.opentalent.fr
+NUXT_PUBLIC_SITE_URL=http://logiciels.test9.opentalent.fr
+
 NUXT_AGENDA_BASE_URL=https://agenda.test9.opentalent.fr
 NUXT_PUBLIC_AGENDA_BASE_URL=https://agenda.test9.opentalent.fr

+ 0 - 20
env/local.portail_v2.opentalent.fr.crt

@@ -1,20 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDNzCCAh8CFFWNApNIhahIkXJWF2im9kz2LqIyMA0GCSqGSIb3DQEBCwUAMFgx
-CzAJBgNVBAYTAlNTMQswCQYDVQQIDAJTUzEPMA0GA1UEBwwGQ2x1c2VzMRcwFQYD
-VQQKDA5PcGVudGFsZW50IERldjESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDUx
-OTEzMDUyOFoXDTI0MDUxODEzMDUyOFowWDELMAkGA1UEBhMCU1MxCzAJBgNVBAgM
-AlNTMQ8wDQYDVQQHDAZDbHVzZXMxFzAVBgNVBAoMDk9wZW50YWxlbnQgRGV2MRIw
-EAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQDG54tqEFfCstJSa6jECdfb9I8Z7WNQmbkJYW5apyisvWXZdBivMg6cepX3CdnK
-LS+1vfHHwy0GWGPyboqe8hbM2goCKKIMkNNPrzjXvkZ8k+N+KfI9hgHXQmtEUXH/
-vj5OMFjeVFJLEsXun5AyWDPAgMSvN4+q8fdA2tRJD4cCbCYk9NN9AMCT40YL3fom
-obCiOL6QKVDqCg3Ed2V4oHUNlVohphTJdaj/s/Qa0CDL3ZfF6btqPzkJHb0YlvdM
-rZI/EgpbyIZb4eBjEtEB6PhrwpL295XAjX5gisKdt6A0gu4G0huylj+61MIe8W7d
-QpvrbzsiO7GWpfVaU3ulAzjfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGKQUZIU
-XuUlGI1cf8/nAitF2a87rqc3OdzJnVfOq0LVAaOKxEE5pKRy1rXV6V/RbX1ZVfUE
-YUHKeQcrVwochPFwHDohiJdiWLMXuWCGbPVWkzQELNgnGgTr/zzdp5OA/e+sPOOy
-aUczkYBmbgADrkKGq1DgjC2bRIjHvfU7L4h2Pf2G353cto66tr/j5G5un2jXxP+7
-XlzNLbl8wsleYHX4lhWnMvHs/pQkWZdPXPOKAXNvefCmjQ2KrKlCa/BLbBuuZfjM
-kB1usIg4xDjeXOFrU9BXkEq02vYfpX3ncmxcK1XQy9t1kDcCuTnNxU/RjyVTkgnc
-Im6/91frXABcRlA=
------END CERTIFICATE-----

+ 0 - 28
env/local.portail_v2.opentalent.fr.key

@@ -1,28 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDG54tqEFfCstJS
-a6jECdfb9I8Z7WNQmbkJYW5apyisvWXZdBivMg6cepX3CdnKLS+1vfHHwy0GWGPy
-boqe8hbM2goCKKIMkNNPrzjXvkZ8k+N+KfI9hgHXQmtEUXH/vj5OMFjeVFJLEsXu
-n5AyWDPAgMSvN4+q8fdA2tRJD4cCbCYk9NN9AMCT40YL3fomobCiOL6QKVDqCg3E
-d2V4oHUNlVohphTJdaj/s/Qa0CDL3ZfF6btqPzkJHb0YlvdMrZI/EgpbyIZb4eBj
-EtEB6PhrwpL295XAjX5gisKdt6A0gu4G0huylj+61MIe8W7dQpvrbzsiO7GWpfVa
-U3ulAzjfAgMBAAECggEADRDOrqPuL+LxP+rEhkQjQtaYw9o9KOFAL2aQCjJq0JYM
-Z7FhobiHDy5aRYUHImm/dY/JnxaqEX2i6xzpzDEY4FH+UE/8/RhSWSyobjuYg7xv
-OC1nNTVlT4+UXkN/Wo5Tp20zmT8uvLG6AMfIgBYdKCsNQHlE3bTRShhV04rKkpUM
-bPhazdGOn8olk2sIn8IRBp6HlDVajew0CJJzF39ndhZ2wlh+xZW5+LSKijNQLWx6
-JhCSmYVZzBvOFmBZQep1G1QQwsY4NM6dIQwPWbEEXD0KWGP4iZo7G5kup35QC6sn
-pTN4URH3zpmEW235C5vomT1U5MymW3z5B8ZsPlLdoQKBgQDq299LtvzdE0LZgOp2
-iEmc8pwtWUUHTU4jYMpY0E5sPZijrdRvnYHOKh7JEIuJNOtM9t8NSNimKC2barGU
-I6zAPyQ4LT7ejtxlYSouKveTDCiEYoJPBHHAWa7dq31SJK6f8+zFzDKkJWQoG2EZ
-b5RSUq638Mz1JuH0N2po0xThjwKBgQDYzyIIFxrgJtjV0rCsEU9J7/rePPYU9/gJ
-vbzjJ09NKzZI3k6TYvz6veKEutSYf0y0oCPLcQc7MufLcXPy0EwaQ2mQXNzZZMnm
-TcaZGh4NOuM5fRG8JUHvwBPWKJTW1Yp9RLilX59wBWUCJEAWEBWgaGkXBGJ6EIqW
-052cQqvrsQKBgQDacxTpehUNFMNTSAPNqMq/vzHhRG1ec1tAsODFZ0vqhgiaBi/Y
-eaqpNqhX4JnJT32louKpmp5ZPnndW4o8/knKr3BjCEK4BC2f5pazRqftEJwHNxF5
-qeqcPU7XRJFWfGQox1vuRxa7c0QLwhu46v4M7HczBDm/tSDqZTkGs39ypQKBgQDP
-lEnWCyEdObRjzMyAgjYy6UR84UsOunfOA87Egc+IZ/YagFYlASKLg6BMSoDf3Qn4
-6lY7I2W0BUIu49oKweoLPz3kDTUaI0i3awHZdksNibMiSJ/hTp65yGJ1qfcPVfa5
-h0FufIYvf8q86S4+sXmzUiRD+Fu82PeH/uYpRSBLkQKBgEXmtaewU1pqyKw69kJA
-lRA38qgdDsFZg1MnjLkP2lgzRmg6bENkNHWHg0jO1rLKbmfNct1yNgHcJXDk8+Gd
-XxYpxpMemzhQgJgKXPoIQU80JCDavCJXD1CZen96HEWxHjUnhhplqk9suXBcJ4Gu
-h7RMKwfik7cqFt+VJkwF7VhG
------END PRIVATE KEY-----

+ 1 - 1
env/setupEnv.mjs

@@ -18,7 +18,7 @@ const projectDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
 const hostname = process.env.HOST ?? os.hostname()
 
 const environments = {
-  'portail_v2': '.env.docker',
+  'site_logiciels': '.env.docker',
   'prod-v2': '.env.prod',
   'test-v2': '.env.test',
   'test1': '.env.test1',

+ 29 - 0
error.vue

@@ -0,0 +1,29 @@
+<template>
+  <main>
+    <div v-if="error">
+      <CommonErrorNotFound v-if="error.statusCode === 404" />
+      <div v-else>Une erreur s'est produite...</div>
+    </div>
+
+    <nuxt-link to="/">
+      <v-btn class="ot-button"> Retour </v-btn>
+    </nuxt-link>
+  </main>
+</template>
+
+<script setup lang="ts">
+import type { NuxtError } from '#app'
+
+const error: Ref<NuxtError | null | undefined> = useError()
+</script>
+
+<style lang="scss">
+.ot-button {
+  border-radius: 6px;
+  height: 48px;
+  font-size: 0.8rem;
+  font-weight: 300;
+  background: var(--secondary-color) !important;
+  color: var(--on-neutral-color) !important;
+}
+</style>

+ 8 - 0
lang/fr.json

@@ -1,2 +1,10 @@
 {
+  "PERMANENT": "CDI",
+  "FIXED_TERM": "CDD",
+  "HOLDER": "Titulaire",
+  "INTERNSHIP": "Stage",
+  "REPLACEMENT": "Remplacement",
+  "TEMPORARY": "Vacataire",
+  "INTERIM": "Intermittent",
+  "OTHER": "Autre"
 }

+ 4 - 1
models/Maestro/JobApplication.ts

@@ -1,4 +1,4 @@
-import { Uid, Str, Attr } from 'pinia-orm/dist/decorators'
+import { Uid, Str, Attr, Num } from 'pinia-orm/dist/decorators'
 import ApiModel from '~/models/ApiModel'
 
 /**
@@ -32,4 +32,7 @@ export default class JobApplication extends ApiModel {
 
   @Str(null)
   declare message: string | null
+
+  @Num(null)
+  declare jobPostingId: number | null
 }

+ 1 - 1
models/Maestro/JobPosting.ts

@@ -7,7 +7,7 @@ import ApiModel from '~/models/ApiModel'
  * @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 = 'public/job_postings'
 
   @Uid()
   declare id: number

+ 1 - 1
models/Maestro/News.ts

@@ -7,7 +7,7 @@ import ApiModel from '~/models/ApiModel'
  * @see https://gitlab.2iopenservice.com/opentalent/maestro/-/blob/master/src/Entity/News/News.php?ref_type=heads
  */
 export default class News extends ApiModel {
-  static entity = 'news'
+  static entity = 'public/news'
 
   @Uid()
   declare id: number | string

+ 25 - 4
nuxt.config.ts

@@ -12,8 +12,8 @@ if (!process.env.NUXT_ENV) {
 
 if (process.env.NUXT_ENV === 'dev') {
   https = {
-    key: fs.readFileSync('env/local.portail_v2.opentalent.fr.key'),
-    cert: fs.readFileSync('env/local.portail_v2.opentalent.fr.crt'),
+    key: fs.readFileSync('env/local.logiciels.opentalent.fr.key'),
+    cert: fs.readFileSync('env/local.logiciels.opentalent.fr.crt'),
   }
 } else {
   transpile.push('lodash')
@@ -25,17 +25,26 @@ if (process.env.NUXT_ENV === 'dev') {
  * @see https://nuxt.com/docs/api/configuration/nuxt-config
  */
 export default defineNuxtConfig({
-  ssr: false,
+  ssr: true,
+  routeRules: {
+    // all routes will be generated at build time and cached permanently
+    '/**': { prerender: true },
+    // these pages will be background revalidated (ISR) at most every 60 seconds
+    '/actualites': { isr: 60 },
+    '/nous-rejoindre': { isr: 60 },
+  },
   title: 'Opentalent',
   runtimeConfig: {
     // Private config that is only available on the server
     env: '',
+    siteUrl: '',
     apiBaseUrl: '',
     agendaBaseUrl: '',
     hCaptchaSiteKey: '35360874-ebb1-4748-86e3-9b156d5bfc53',
     // Config within public will be also exposed to the client
     public: {
       env: '',
+      siteUrl: '',
       apiBaseUrl: '',
       agendaBaseUrl: '',
       hCaptchaSiteKey: '35360874-ebb1-4748-86e3-9b156d5bfc53',
@@ -51,7 +60,6 @@ export default defineNuxtConfig({
       meta: [
         { charset: 'utf-8' },
         { name: 'viewport', content: 'width=device-width, initial-scale=1' },
-        { hid: 'description', name: 'description', content: '' },
       ],
       link: [
         { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
@@ -97,6 +105,9 @@ export default defineNuxtConfig({
     '@nuxtjs/i18n',
     '@nuxt/devtools',
     'nuxt3-leaflet',
+    '@nuxtjs/google-fonts',
+    '@nuxtjs/sitemap',
+    'nuxt-gtag',
   ],
   router: {
     options: {
@@ -154,4 +165,14 @@ export default defineNuxtConfig({
   build: {
     transpile,
   },
+  googleFonts: {
+    families: {
+      Barlow: true,
+    },
+    display: 'block',
+  },
+  gtag: {
+    id: 'G-L8PZ9TEFNX',
+    enabled: false,
+  },
 })

+ 11 - 12
package.json

@@ -1,8 +1,8 @@
 {
-  "name": "website",
+  "name": "site_logiciels",
   "version": "0.4.0",
   "description": "Site présentant les logiciels et services Opentalent",
-  "repository": "https://gitlab.2iopenservice.com/opentalent/portail_v2",
+  "repository": "https://gitlab.2iopenservice.com/opentalent/site_logiciels",
   "private": true,
   "type": "module",
   "engines": {
@@ -12,16 +12,11 @@
     "setupenv": "node ./env/setupEnv.mjs",
     "dev": "yarn setupenv && nuxt dev",
     "generate": "yarn setupenv && nuxt generate",
+    "build": "yarn setupenv && nuxt build",
     "prepare": "yarn setupenv && nuxt prepare",
-    "dev:local": "yarn dev --dotenv .env.local",
-    "dev:prod": "yarn dev --dotenv .env.prod",
-    "dev:ci": "yarn dev --dotenv .env.ci",
-    "build": "nuxt build",
-    "build:local": "yarn build --dotenv .env.local",
-    "build:prod": "yarn build --dotenv .env.prod",
-    "deploy": "git pull && yarn install && yarn generate",
-    "lint": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore .",
-    "test": "jest"
+    "start": "nuxt start",
+    "deploy": "git pull && yarn install && yarn generate && yarn build && sudo supervisorctl restart site_logiciels:site_logiciels_00",
+    "lint": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore ."
   },
   "dependencies": {
     "@fortawesome/fontawesome-free": "^6.5.1",
@@ -30,7 +25,9 @@
     "@fortawesome/free-solid-svg-icons": "^6.5.1",
     "@hcaptcha/vue3-hcaptcha": "^1.3.0",
     "@nuxtjs/date-fns": "^1.5.0",
+    "@nuxtjs/google-fonts": "^3.2.0",
     "@nuxtjs/i18n": "^8.1.1",
+    "@nuxtjs/sitemap": "^5.2.0",
     "@pinia-orm/nuxt": "^1.7.0",
     "@pinia/nuxt": "^0.5.1",
     "@splidejs/vue-splide": "^0.6.12",
@@ -44,6 +41,7 @@
     "leaflet": "^1.9.3",
     "libphonenumber-js": "^1.10.55",
     "nuxt": "^3.11.2",
+    "nuxt-gtag": "^2.0.6",
     "nuxt-lodash": "^2.5.3",
     "nuxt3-leaflet": "^1.0.12",
     "ofetch": "^1.3.3",
@@ -54,7 +52,8 @@
     "uuid": "^9.0.1",
     "vite-plugin-vuetify": "^2.0.3",
     "vue3-carousel": "^0.3.1",
-    "vuetify": "^3.5.15"
+    "vue3-cookies": "^1.0.6",
+    "vuetify": "^3.6.7"
   },
   "devDependencies": {
     "@nuxt/devtools": "^1.0.8",

+ 0 - 0
pages/actualites/[id].vue → pages/actualites/[id].client.vue


+ 1 - 1
pages/CGV.vue → pages/cgv.vue

@@ -8,7 +8,7 @@
     <LayoutUITitlePage> Conditions générales de vente </LayoutUITitlePage>
 
     <CommonBanner
-      image-src="/images/Bannieres_Mentions_legales-CGV-Cookies.png"
+      image-src="/images/pages/legal/Banniere_informations_legales.jpg"
       image-alt="Ordinateur à côté d'une balance d'un livre et d'un maillet en bois"
     />
 

+ 1 - 1
pages/formations.vue

@@ -13,7 +13,7 @@
     </LayoutUITitlePage>
 
     <CommonBanner
-      image-src="/images/pages/formations/banner/Formations_Opentalent.jpg"
+      image-src="/images/pages/formations/banner/Formations_Opentalent.webp"
       image-alt="Formation dans une salle de réunion ou 5 personnes regardent un écran"
     />
 

+ 1 - 1
pages/mentions-legales.vue

@@ -8,7 +8,7 @@
     <LayoutUITitlePage> Mentions légales </LayoutUITitlePage>
 
     <CommonBanner
-      image-src="/images/Bannieres_Mentions_legales-CGV-Cookies.png"
+      image-src="/images/pages/legal/Banniere_informations_legales.jpg"
       image-alt="Ordinateur à côté d'une balance d'un livre et d'un maillet en bois"
     />
 

+ 1 - 1
pages/nous-contacter.vue

@@ -14,7 +14,7 @@
     </LayoutUITitlePage>
 
     <CommonBanner
-      image-src="/images/pages/contact/banner/Contactez_nous-visuel_Opentalent.jpg"
+      image-src="/images/pages/contact/banner/Contactez_nous_Opentalent.webp"
       image-alt="Icône téléphone, enveloppe, arobase, bulles de discussion et homme sur son smartphone et son ordinateur portable"
     />
 

+ 1 - 1
pages/nous-rejoindre/[id].vue → pages/nous-rejoindre/[id].client.vue

@@ -10,7 +10,7 @@
     </LayoutUITitlePage>
 
     <CommonBanner
-      image-src="/images/pages/join-us/banner/Rejoindre_Opentalent.jpg"
+      image-src="/images/pages/join-us/banner/Rejoindre_Opentalent.webp"
       image-alt="banner"
     />
 

+ 1 - 1
pages/nous-rejoindre/index.vue

@@ -13,7 +13,7 @@
     </LayoutUITitlePage>
 
     <CommonBanner
-      image-src="/images/pages/join-us/banner/Rejoindre_Opentalent.jpg"
+      image-src="/images/pages/join-us/banner/Rejoindre_Opentalent.webp"
       image-alt="Mains de femmes les unes au-dessus des autres"
     />
 

Some files were not shown because too many files changed in this diff