Browse Source

improve error management

Olivier Massot 5 months ago
parent
commit
1ab1e084a9

+ 2 - 1
composables/data/useAp2iRequestService.ts

@@ -9,7 +9,7 @@ import ApiRequestService from '~/services/data/apiRequestService'
  */
 let apiRequestServiceClass: null | ApiRequestService = null
 
-export const useAp2iRequestService = () => {
+export const useAp2iRequestService = (retry: number | false = 1) => {
   const runtimeConfig = useRuntimeConfig()
 
   const baseURL = runtimeConfig.ap2iBaseUrl ?? runtimeConfig.public.ap2iBaseUrl
@@ -48,6 +48,7 @@ export const useAp2iRequestService = () => {
     baseURL,
     onRequest,
     onResponse,
+    retry,
   }
 
   // Avoid memory leak

+ 69 - 0
composables/utils/useAp2iErrorHandler.ts

@@ -0,0 +1,69 @@
+import { useNuxtApp } from '#app'
+
+/**
+ * Composable for handling API errors, especially for organization-related errors
+ */
+export const useAp2iErrorHandler = () => {
+  const { $i18n } = useNuxtApp()
+
+  /**
+   * Process API error and extract meaningful error message
+   * @param error - The error object from API response
+   * @returns Processed error message
+   */
+  const processApiError = (error: unknown): string => {
+    let errorMessage =
+      "Une erreur s'est produite. Veuillez réessayer plus tard ou nous contacter directement."
+
+    // Try to extract the specific error message from the API response
+    if (
+      error &&
+      typeof error === 'object' &&
+      'data' in error &&
+      error.data &&
+      typeof error.data === 'object'
+    ) {
+      const errorData = error.data as { detail?: string }
+      if (errorData.detail) {
+        // Check if it's the specific error about organization already existing
+        const organizationExistsRegex =
+          /Handling ".*" failed: An organization named '(.+)' already exists in (.+)/
+        const match = errorData.detail.match(organizationExistsRegex)
+
+        if (match) {
+          // Extract the organization name and city name and use the translation
+          const organizationName = match[1]
+          const cityName = match[2]
+          const translationKey =
+            "An organization named '%s' already exists in %s"
+
+          const translatedMessage = $i18n.t(translationKey, [
+            organizationName,
+            cityName,
+          ])
+          errorMessage = translatedMessage as string
+        } else {
+          // For other errors, use translation if available, otherwise use the original message
+          if (!errorData.detail) return errorData.detail
+
+          // Remove the "Handling ... failed:" part if present
+          let cleanedDetail = errorData.detail
+          const handlingFailedRegex = /Handling ".*" failed: (.*)/
+          const handlingMatch = cleanedDetail.match(handlingFailedRegex)
+
+          if (handlingMatch) {
+            cleanedDetail = handlingMatch[1]
+          }
+
+          errorMessage = ($i18n.t(cleanedDetail) || cleanedDetail) as string
+        }
+      }
+    }
+
+    return errorMessage
+  }
+
+  return {
+    processApiError,
+  }
+}

+ 4 - 1
lang/fr.json

@@ -7,5 +7,8 @@
   "TEMPORARY": "Vacataire",
   "INTERIM": "Intermittent",
   "OTHER": "Autre",
-  "Invalid phone number": "Numéro de téléphone invalide"
+  "Invalid phone number": "Numéro de téléphone invalide",
+  "Request expired: submission date is more than 15 minutes old": "Requête expirée : la date de soumission date de plus de 15 minutes",
+  "An organization named '%s' already exists in %s": "Une organisation nommée '{0}' existe déjà à {1}",
+  "Invalid request status": "Statut de la requête invalide"
 }

+ 381 - 387
pages/shop/try/artist-premium.vue

@@ -15,385 +15,389 @@
               Essayez gratuitement Opentalent Artist Premium pendant 30 jours !
             </h1>
 
-            <div class="description mb-8">
-              <p>
-                Opentalent Artist Premium est une solution en ligne complète,
-                pensée pour les orchestres, chorales, compagnies de théâtre, de
-                danse ou de cirque. Elle vous aide à gagner du temps dans
-                l'organisation de vos activités, à mieux collaborer avec vos
-                équipes et à renforcer votre visibilité auprès de votre public.
-              </p>
-
-              <p>
-                Pendant 30 jours, profitez de toutes les fonctionnalités
-                d'Opentalent Artist Premium, gratuitement et sans engagement :
-              </p>
-
-              <ul class="benefits-list">
-                <li>
-                  <span class="mr-1">✔️</span> Gestion intuitive des membres et
-                  des événements
-                </li>
-                <li>
-                  <span class="mr-1">✔️</span> Planification avancée des
-                  répétitions, spectacles et tournées
-                </li>
-                <li>
-                  <span class="mr-1">✔️</span> Outils de communication intégrés
-                  (emails, publipostage, etc.)
-                </li>
-                <li>
-                  <span class="mr-1">✔️</span> Site web personnalisable pour
-                  présenter vos projets et votre structure
-                </li>
-                <li>
-                  <span class="mr-1">✔️</span> Accès collaboratif pour vos
-                  équipes, en temps réel
-                </li>
-              </ul>
-
-              <p>
-                Il vous suffit de remplir le formulaire ci-dessous pour activer
-                votre essai gratuit.
-              </p>
-
-              <p>
-                Lancez-vous dès aujourd'hui et découvrez comment Opentalent peut
-                transformer votre organisation artistique !
-              </p>
-            </div>
+            <div v-if="!trialRequestSent">
+              <div class="description mb-8">
+                <p>
+                  Opentalent Artist Premium est une solution en ligne complète,
+                  pensée pour les orchestres, chorales, compagnies de théâtre,
+                  de danse ou de cirque. Elle vous aide à gagner du temps dans
+                  l'organisation de vos activités, à mieux collaborer avec vos
+                  équipes et à renforcer votre visibilité auprès de votre
+                  public.
+                </p>
 
-            <v-form
-              v-if="!trialRequestSent"
-              ref="form"
-              validate-on="submit lazy"
-              @submit.prevent="submit"
-            >
-              <v-container>
-                <div v-if="isDevelopment" class="dev-tools-container">
-                  <v-btn
-                    color="info"
-                    size="small"
-                    prepend-icon="fa fa-magic"
-                    @click="fillWithDummyData"
-                  >
-                    Remplir avec des données de test
-                  </v-btn>
-                </div>
-                <i
-                  >Les champs dont le nom est suivi d'un astérisque (*) sont
-                  obligatoires.</i
-                >
-
-                <h2 class="section-title">Coordonnées de la structure</h2>
-
-                <!-- Structure name -->
-                <v-row>
-                  <v-col cols="12">
-                    <v-text-field
-                      v-model="trialRequest.structureName"
-                      :rules="[validateRequired]"
-                      label="Nom de la structure*"
-                      required
-                      @input="onStructureNameUpdated"
-                    />
-                  </v-col>
-                </v-row>
-
-                <!-- Structure address -->
-                <v-row>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.address"
-                      :rules="[validateRequired]"
-                      label="Adresse siège social de la structure*"
-                      required
-                    />
-                  </v-col>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.addressComplement"
-                      label="Adresse (suite)"
-                    />
-                  </v-col>
-                </v-row>
-
-                <v-row>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.postalCode"
-                      :rules="[validateRequired, validatePostalCode]"
-                      label="Code postal*"
-                      required
-                    />
-                  </v-col>
-
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.city"
-                      :rules="[validateRequired]"
-                      label="Ville*"
-                      required
-                    />
-                  </v-col>
-                </v-row>
-
-                <!-- Structure email and SIREN -->
-                <v-row>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.structureEmail"
-                      :rules="[validateRequired, validateEmail]"
-                      label="Adresse mail de la structure*"
-                      required
-                      type="email"
-                    />
-                  </v-col>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.siren"
-                      :rules="[validateSiren]"
-                      label="SIREN (optionnel)"
-                      hint="Numéro à 9 chiffres"
-                    />
-                  </v-col>
-                </v-row>
-
-                <!-- Structure type and legal status -->
-                <v-row>
-                  <v-col cols="12" md="6">
-                    <v-select
-                      v-model="trialRequest.structureType"
-                      :rules="[validateRequired]"
-                      label="Type de la structure*"
-                      :items="structureTypes"
-                      item-value="value"
-                      item-title="title"
-                      required
-                    />
-                  </v-col>
-                  <v-col cols="12" md="6">
-                    <v-select
-                      v-model="trialRequest.legalStatus"
-                      :rules="[validateRequired]"
-                      label="Statut juridique*"
-                      :items="legalStatuses"
-                      item-value="value"
-                      item-title="title"
-                      required
-                    />
-                  </v-col>
-                </v-row>
-
-                <h2 class="section-title">Représentée par</h2>
-
-                <!-- Representative function -->
-                <v-row>
-                  <v-col cols="12">
-                    <v-text-field
-                      v-model="trialRequest.representativeFunction"
-                      :rules="[validateRequired]"
-                      label="Fonction*"
-                      required
-                    />
-                  </v-col>
-                </v-row>
-
-                <!-- Representative name -->
-                <v-row>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.representativeFirstName"
-                      :rules="[validateRequired]"
-                      label="Prénom*"
-                      required
-                    />
-                  </v-col>
-
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.representativeLastName"
-                      :rules="[validateRequired]"
-                      label="Nom*"
-                      required
-                    />
-                  </v-col>
-                </v-row>
-
-                <!-- Representative contact -->
-                <v-row>
-                  <v-col cols="12" md="6">
-                    <v-text-field
-                      v-model="trialRequest.representativeEmail"
-                      :rules="[validateRequired, validateEmail]"
-                      label="Adresse mail*"
-                      required
-                      type="email"
-                    />
-                  </v-col>
-
-                  <v-col cols="12" md="6">
-                    <CommonPhoneInput
-                      ref="phoneInput"
-                      v-model="trialRequest.representativePhone"
-                      label="Téléphone*"
-                      required
-                    />
-                  </v-col>
-                </v-row>
-
-                <h2 class="section-title mb-6">Informations de connexion</h2>
-
-                <!-- Structure identifier -->
-                <v-row>
-                  <v-col cols="12" md="6" class="mx-auto">
-                    <v-text-field
-                      v-model="trialRequest.structureIdentifier"
-                      :rules="[
-                        validateRequired,
-                        validateSubdomain,
-                        validateSubdomainAvailability,
-                      ]"
-                      label="Identifiant de la structure*"
-                      required
-                      class="text-center"
-                      @input="onStructureIdentifierUpdated"
-                    />
-                    <div class="validationMessage">
-                      <span v-if="validationPending">
-                        <v-progress-circular size="16" indeterminate />
-                        <i class="ml-2">Vérification en cours</i>
-                      </span>
-                      <span
-                        v-else-if="subdomainAvailable === true"
-                        class="text-success"
-                      >
-                        <v-icon>fa fa-check</v-icon>
-                        <i class="ml-2"> Cet identifiant est disponible</i>
-                      </span>
-                      <span
-                        v-else-if="subdomainAvailable === false"
-                        class="text-error"
-                      >
-                        <v-icon>fa fa-x</v-icon>
-                        <i class="ml-2">Cet identifiant n'est pas disponible</i>
-                      </span>
-                    </div>
-                    <div class="mt-2">
-                      <i v-if="trialRequest.structureIdentifier">
-                        Le compte administrateur de la structure sera
-                        <strong>
-                          admin{{ trialRequest.structureIdentifier }}
-                        </strong>
-                      </i>
-                    </div>
-                    <div>
-                      <i>
-                        Veuillez renseigner un mot de passe pour ce compte :
-                      </i>
-                    </div>
-                  </v-col>
-                </v-row>
-
-                <!-- Password field -->
-                <v-row>
-                  <v-col cols="12" md="6" class="mx-auto">
-                    <v-text-field
-                      v-model="trialRequest.password"
-                      :rules="[validateRequired, validatePassword]"
-                      label="Mot de passe*"
-                      required
-                      :type="showPassword ? 'text' : 'password'"
-                      :append-inner-icon="
-                        showPassword ? 'fa fa-eye-slash' : 'fa fa-eye'
-                      "
-                      @click:append-inner="showPassword = !showPassword"
-                    />
-                    <div class="mt-1">
-                      <i>
-                        Le mot de passe doit contenir au moins 8 caractères, une
-                        minuscule, une majuscule, un chiffre et un caractère
-                        spécial.
-                      </i>
-                    </div>
-                  </v-col>
-                </v-row>
-
-                <h2 class="section-title">Accord de termes et conditions</h2>
-
-                <!-- Terms checkboxes -->
-                <v-checkbox
-                  v-model="trialRequest.termsAccepted"
-                  :rules="[validateCheckbox]"
-                  required
-                >
-                  <template #label>
-                    Mon organisme accepte les &nbsp;
-                    <a
-                      href="https://maestro.opentalent.fr/uploads/share/Documents_juridique/CGU.pdf"
-                      target="_blank"
+                <p>
+                  Pendant 30 jours, profitez de toutes les fonctionnalités
+                  d'Opentalent Artist Premium, gratuitement et sans engagement :
+                </p>
+
+                <ul class="benefits-list">
+                  <li>
+                    <span class="mr-1">✔️</span> Gestion intuitive des membres
+                    et des événements
+                  </li>
+                  <li>
+                    <span class="mr-1">✔️</span> Planification avancée des
+                    répétitions, spectacles et tournées
+                  </li>
+                  <li>
+                    <span class="mr-1">✔️</span> Outils de communication
+                    intégrés (emails, publipostage, etc.)
+                  </li>
+                  <li>
+                    <span class="mr-1">✔️</span> Site web personnalisable pour
+                    présenter vos projets et votre structure
+                  </li>
+                  <li>
+                    <span class="mr-1">✔️</span> Accès collaboratif pour vos
+                    équipes, en temps réel
+                  </li>
+                </ul>
+
+                <p>
+                  Il vous suffit de remplir le formulaire ci-dessous pour
+                  activer votre essai gratuit.
+                </p>
+
+                <p>
+                  Lancez-vous dès aujourd'hui et découvrez comment Opentalent
+                  peut transformer votre organisation artistique !
+                </p>
+              </div>
+
+              <v-form
+                ref="form"
+                validate-on="submit lazy"
+                @submit.prevent="submit"
+              >
+                <v-container>
+                  <div v-if="isDevelopment" class="dev-tools-container">
+                    <v-btn
+                      color="info"
+                      size="small"
+                      prepend-icon="fa fa-magic"
+                      @click="fillWithDummyData"
                     >
-                      conditions générales d'utilisation </a
-                    >.*
-                  </template>
-                </v-checkbox>
-
-                <v-checkbox
-                  v-model="trialRequest.legalRepresentative"
-                  :rules="[validateCheckbox]"
-                  label="J'agis en tant que représentant légal de l'association ou de la structure.*"
-                  required
-                />
-
-                <v-checkbox
-                  v-model="trialRequest.newsletterSubscription"
-                  label="J'accepte de recevoir la lettre d'information culturelle afin de découvrir des idées de sorties adaptées à ma région."
-                />
-
-                <div class="d-flex flex-row justify-center">
-                  <LayoutCaptcha />
-                </div>
+                      Remplir avec des données de test
+                    </v-btn>
+                  </div>
+                  <i
+                    >Les champs dont le nom est suivi d'un astérisque (*) sont
+                    obligatoires.</i
+                  >
 
-                <!-- Submit Button -->
-                <div class="d-flex flex-row justify-center my-10">
-                  <v-btn
-                    type="submit"
-                    color="secondary"
-                    size="large"
-                    class="submit-btn"
+                  <h2 class="section-title">Coordonnées de la structure</h2>
+
+                  <!-- Structure name -->
+                  <v-row>
+                    <v-col cols="12">
+                      <v-text-field
+                        v-model="trialRequest.structureName"
+                        :rules="[validateRequired]"
+                        label="Nom de la structure*"
+                        required
+                        @input="onStructureNameUpdated"
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <!-- Structure address -->
+                  <v-row>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.address"
+                        :rules="[validateRequired]"
+                        label="Adresse siège social de la structure*"
+                        required
+                      />
+                    </v-col>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.addressComplement"
+                        label="Adresse (suite)"
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <v-row>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.postalCode"
+                        :rules="[validateRequired, validatePostalCode]"
+                        label="Code postal*"
+                        required
+                      />
+                    </v-col>
+
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.city"
+                        :rules="[validateRequired]"
+                        label="Ville*"
+                        required
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <!-- Structure email and SIREN -->
+                  <v-row>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.structureEmail"
+                        :rules="[validateRequired, validateEmail]"
+                        label="Adresse mail de la structure*"
+                        required
+                        type="email"
+                      />
+                    </v-col>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.siren"
+                        :rules="[validateSiren]"
+                        label="SIREN (optionnel)"
+                        hint="Numéro à 9 chiffres"
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <!-- Structure type and legal status -->
+                  <v-row>
+                    <v-col cols="12" md="6">
+                      <v-select
+                        v-model="trialRequest.structureType"
+                        :rules="[validateRequired]"
+                        label="Type de la structure*"
+                        :items="structureTypes"
+                        item-value="value"
+                        item-title="title"
+                        required
+                      />
+                    </v-col>
+                    <v-col cols="12" md="6">
+                      <v-select
+                        v-model="trialRequest.legalStatus"
+                        :rules="[validateRequired]"
+                        label="Statut juridique*"
+                        :items="legalStatuses"
+                        item-value="value"
+                        item-title="title"
+                        required
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <h2 class="section-title">Représentée par</h2>
+
+                  <!-- Representative function -->
+                  <v-row>
+                    <v-col cols="12">
+                      <v-text-field
+                        v-model="trialRequest.representativeFunction"
+                        :rules="[validateRequired]"
+                        label="Fonction*"
+                        required
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <!-- Representative name -->
+                  <v-row>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.representativeFirstName"
+                        :rules="[validateRequired]"
+                        label="Prénom*"
+                        required
+                      />
+                    </v-col>
+
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.representativeLastName"
+                        :rules="[validateRequired]"
+                        label="Nom*"
+                        required
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <!-- Representative contact -->
+                  <v-row>
+                    <v-col cols="12" md="6">
+                      <v-text-field
+                        v-model="trialRequest.representativeEmail"
+                        :rules="[validateRequired, validateEmail]"
+                        label="Adresse mail*"
+                        required
+                        type="email"
+                      />
+                    </v-col>
+
+                    <v-col cols="12" md="6">
+                      <CommonPhoneInput
+                        ref="phoneInput"
+                        v-model="trialRequest.representativePhone"
+                        label="Téléphone*"
+                        required
+                      />
+                    </v-col>
+                  </v-row>
+
+                  <h2 class="section-title mb-6">Informations de connexion</h2>
+
+                  <!-- Structure identifier -->
+                  <v-row>
+                    <v-col cols="12" md="6" class="mx-auto">
+                      <v-text-field
+                        v-model="trialRequest.structureIdentifier"
+                        :rules="[
+                          validateRequired,
+                          validateSubdomain,
+                          validateSubdomainAvailability,
+                        ]"
+                        label="Identifiant de la structure*"
+                        required
+                        class="text-center"
+                        @input="onStructureIdentifierUpdated"
+                      />
+                      <div class="validationMessage">
+                        <span v-if="validationPending">
+                          <v-progress-circular size="16" indeterminate />
+                          <i class="ml-2">Vérification en cours</i>
+                        </span>
+                        <span
+                          v-else-if="subdomainAvailable === true"
+                          class="text-success"
+                        >
+                          <v-icon>fa fa-check</v-icon>
+                          <i class="ml-2"> Cet identifiant est disponible</i>
+                        </span>
+                        <span
+                          v-else-if="subdomainAvailable === false"
+                          class="text-error"
+                        >
+                          <v-icon>fa fa-x</v-icon>
+                          <i class="ml-2"
+                            >Cet identifiant n'est pas disponible</i
+                          >
+                        </span>
+                      </div>
+                      <div class="mt-2">
+                        <i v-if="trialRequest.structureIdentifier">
+                          Le compte administrateur de la structure sera
+                          <strong>
+                            admin{{ trialRequest.structureIdentifier }}
+                          </strong>
+                        </i>
+                      </div>
+                      <div>
+                        <i>
+                          Veuillez renseigner un mot de passe pour ce compte :
+                        </i>
+                      </div>
+                    </v-col>
+                  </v-row>
+
+                  <!-- Password field -->
+                  <v-row>
+                    <v-col cols="12" md="6" class="mx-auto">
+                      <v-text-field
+                        v-model="trialRequest.password"
+                        :rules="[validateRequired, validatePassword]"
+                        label="Mot de passe*"
+                        required
+                        :type="showPassword ? 'text' : 'password'"
+                        :append-inner-icon="
+                          showPassword ? 'fa fa-eye-slash' : 'fa fa-eye'
+                        "
+                        @click:append-inner="showPassword = !showPassword"
+                      />
+                      <div class="mt-1">
+                        <i>
+                          Le mot de passe doit contenir au moins 8 caractères,
+                          une minuscule, une majuscule, un chiffre et un
+                          caractère spécial.
+                        </i>
+                      </div>
+                    </v-col>
+                  </v-row>
+
+                  <h2 class="section-title">Accord de termes et conditions</h2>
+
+                  <!-- Terms checkboxes -->
+                  <v-checkbox
+                    v-model="trialRequest.termsAccepted"
+                    :rules="[validateCheckbox]"
+                    required
                   >
-                    COMMENCER MON ESSAI DE 30 JOURS
-                  </v-btn>
-                </div>
+                    <template #label>
+                      Mon organisme accepte les &nbsp;
+                      <a
+                        href="https://maestro.opentalent.fr/uploads/share/Documents_juridique/CGU.pdf"
+                        target="_blank"
+                      >
+                        conditions générales d'utilisation </a
+                      >.*
+                    </template>
+                  </v-checkbox>
+
+                  <v-checkbox
+                    v-model="trialRequest.legalRepresentative"
+                    :rules="[validateCheckbox]"
+                    label="J'agis en tant que représentant légal de l'association ou de la structure.*"
+                    required
+                  />
 
-                <p class="text-center no-credit-card">
-                  Aucune carte de crédit requise. En cliquant sur "Commencer mon
-                  essai de 30 jours", vous acceptez de démarrer votre période
-                  d'essai gratuit.
-                </p>
+                  <v-checkbox
+                    v-model="trialRequest.newsletterSubscription"
+                    label="J'accepte de recevoir la lettre d'information culturelle afin de découvrir des idées de sorties adaptées à ma région."
+                  />
 
-                <div v-if="validationError" class="error">
-                  Des champs du formulaire sont invalides ou manquants.
-                </div>
+                  <div class="d-flex flex-row justify-center">
+                    <LayoutCaptcha />
+                  </div>
+
+                  <!-- Submit Button -->
+                  <div class="d-flex flex-row justify-center my-10">
+                    <v-btn
+                      type="submit"
+                      color="secondary"
+                      size="large"
+                      class="submit-btn"
+                    >
+                      COMMENCER MON ESSAI DE 30 JOURS
+                    </v-btn>
+                  </div>
+
+                  <p class="text-center no-credit-card">
+                    Aucune carte de crédit requise. En cliquant sur "Commencer
+                    mon essai de 30 jours", vous acceptez de démarrer votre
+                    période d'essai gratuit.
+                  </p>
 
-                <div v-if="errorMsg" class="error">
-                  {{ errorMsg }}
+                  <div v-if="validationError" class="error">
+                    Des champs du formulaire sont invalides ou manquants.
+                  </div>
+
+                  <div v-if="errorMsg" class="error">
+                    {{ errorMsg }}
+                  </div>
+                </v-container>
+
+                <div class="legal">
+                  Les données recueillies par Opentalent sont utilisées pour le
+                  traitement de votre demande et pour vous informer sur nos
+                  offres. Elles sont destinées aux services Opentalent et à ses
+                  sous-traitants pour l'exécution des contrats. Conformément à
+                  la loi "Informatique et Libertés du 6 Janvier 1978", vous
+                  disposez d'un droit d'accès, de modifications, de
+                  rectification et de suppression des données vous concernant.
+                  Pour toute demande, adressez-vous à : OPENTALENT, 265 rue de
+                  la Grange 74950 SCIONZIER - FRANCE, opentalent.fr s'engage à
+                  la confidentialité et à la protection de vos données."
                 </div>
-              </v-container>
-
-              <div class="legal">
-                Les données recueillies par Opentalent sont utilisées pour le
-                traitement de votre demande et pour vous informer sur nos
-                offres. Elles sont destinées aux services Opentalent et à ses
-                sous-traitants pour l'exécution des contrats. Conformément à la
-                loi "Informatique et Libertés du 6 Janvier 1978", vous disposez
-                d'un droit d'accès, de modifications, de rectification et de
-                suppression des données vous concernant. Pour toute demande,
-                adressez-vous à : OPENTALENT, 265 rue de la Grange 74950
-                SCIONZIER - FRANCE, opentalent.fr s'engage à la confidentialité
-                et à la protection de vos données."
-              </div>
-            </v-form>
+              </v-form>
+            </div>
 
             <div
               v-else
@@ -442,10 +446,11 @@ import { useRouter } from 'vue-router'
 import type { Ref } from 'vue'
 import { reactive } from 'vue'
 import _ from 'lodash'
-import { useRuntimeConfig, useAsyncData, useNuxtApp } from '#app'
+import { useRuntimeConfig, useAsyncData } from '#app'
 import type { TrialRequest } from '~/types/interface'
 import { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import { useAp2iErrorHandler } from '~/composables/utils/useAp2iErrorHandler'
 import {
   convertPhoneNumberToInternationalFormat,
   slugify,
@@ -472,6 +477,7 @@ const legalStatuses = Object.values(LEGAL_STATUS)
 
 // Get apiRequestService for subdomain availability check
 const { ap2iRequestService } = useAp2iRequestService()
+const { processApiError } = useAp2iErrorHandler()
 
 // Check if we're in a development environment
 const config = useRuntimeConfig()
@@ -688,21 +694,9 @@ const submit = async (): Promise<void> => {
     errorMsg.value =
       "Une erreur s'est produite lors de l'activation de votre essai. Veuillez réessayer plus tard ou nous contacter directement."
 
-    // Try to extract the specific error message from the API response
-    if (
-      e &&
-      typeof e === 'object' &&
-      'data' in e &&
-      e.data &&
-      typeof e.data === 'object'
-    ) {
-      const errorData = e.data as { detail?: string }
-      if (errorData.detail) {
-        const { $i18n } = useNuxtApp()
-        // Use translation if available, otherwise use the original message
-        errorMsg.value += '\n' + ($i18n.t(errorData.detail) || errorData.detail)
-      }
-    }
+    // Process the error message using the common error handler
+    const processedError = processApiError(e)
+    errorMsg.value += '\n' + processedError
   }
 }
 

+ 24 - 30
pages/shop/try/validation.vue

@@ -50,20 +50,22 @@
 
 <script setup lang="ts">
 import { useRoute } from 'vue-router'
-import { useNuxtApp } from '#app'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import { useAp2iErrorHandler } from '~/composables/utils/useAp2iErrorHandler'
+import UrlUtils from '~/services/utils/urlUtils'
 
 const route = useRoute()
-const { ap2iRequestService } = useAp2iRequestService()
+const { ap2iRequestService } = useAp2iRequestService(false)
+const runtimeConfig = useRuntimeConfig()
+const { processApiError } = useAp2iErrorHandler()
 
 const loading = ref(true)
-const validationSuccess = ref(false)
-const errorMsg = ref(
-  "Une erreur s'est produite lors de la validation de votre demande."
-)
+const validationSuccess: Ref<boolean | null> = ref(null)
+const errorMsg: Ref<string | null> = ref(null)
 
 // UUID validation regex
-const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
+const uuidRegex =
+  /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
 
 // Get token from query parameters
 const token = computed(() => {
@@ -71,50 +73,42 @@ const token = computed(() => {
   return typeof queryToken === 'string' ? queryToken : ''
 })
 
-// Validate token and make API request
-onMounted(async () => {
+const validate = async () => {
   try {
     // Check if token is present
     if (!token.value) {
       errorMsg.value = "Aucun jeton de validation n'a été fourni."
-      loading.value = false
       return
     }
 
     // Validate token format (UUID)
     if (!uuidRegex.test(token.value)) {
       errorMsg.value = 'Le format du jeton de validation est invalide.'
-      loading.value = false
       return
     }
 
     // Make API request to validate token
-    await ap2iRequestService.get('/api/public/shop/validate/' + token.value)
+    await ap2iRequestService.get(
+      UrlUtils.join(
+        runtimeConfig.public.ap2iBaseUrl,
+        '/api/public/shop/validate/',
+        token.value
+      )
+    )
 
     // If we get here, the validation was successful
     validationSuccess.value = true
-    loading.value = false
   } catch (error) {
     console.error('Error validating token:', error)
+    validationSuccess.value = false
 
-    // Try to extract the specific error message from the API response
-    if (
-      error &&
-      typeof error === 'object' &&
-      'data' in error &&
-      error.data &&
-      typeof error.data === 'object'
-    ) {
-      const errorData = error.data as { detail?: string }
-      if (errorData.detail) {
-        const { $i18n } = useNuxtApp()
-        // Use translation if available, otherwise use the original message
-        errorMsg.value = $i18n.t(errorData.detail) || errorData.detail
-      }
-    }
-
-    loading.value = false
+    errorMsg.value = processApiError(error)
   }
+}
+
+onMounted(async () => {
+  await validate()
+  loading.value = false
 })
 </script>