Browse Source

Enhances trial form and API request service

Adds a public environment variable to environment files.

Updates the phone number validation and adds international format conversion.

Improves the trial artist premium form by adding a dummy data fill button in development mode, and updates the error handling to display translated API error messages.

Updates the api request service to handle PATCH requests properly.

Updates libphonenumber-js dependency.
Olivier Massot 5 months ago
parent
commit
a03584b049

+ 1 - 0
env/.env.ci

@@ -1,5 +1,6 @@
 ## LOCAL ENVIRONMENT FILE
 ## LOCAL ENVIRONMENT FILE
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.docker

@@ -1,5 +1,6 @@
 ## LOCAL ENVIRONMENT FILE
 ## LOCAL ENVIRONMENT FILE
 NUXT_ENV=dev
 NUXT_ENV=dev
+NUXT_PUBLIC_ENV=dev
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.prod

@@ -1,6 +1,7 @@
 ## PROD ENVIRONMENT FILE
 ## PROD ENVIRONMENT FILE
 # /!\ -- USE ONLY IN PRODUCTION --
 # /!\ -- USE ONLY IN PRODUCTION --
 NUXT_ENV=production
 NUXT_ENV=production
+NUXT_PUBLIC_ENV=production
 NUXT_DEBUG=0
 NUXT_DEBUG=0
 
 
 PORT=3003
 PORT=3003

+ 1 - 0
env/.env.test

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test1

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test2

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test3

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test4

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test5

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test6

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test7

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test8

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 1 - 0
env/.env.test9

@@ -1,4 +1,5 @@
 NUXT_ENV=test
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 NUXT_DEBUG=1
 DEBUG=1
 DEBUG=1
 
 

+ 2 - 1
lang/fr.json

@@ -6,5 +6,6 @@
   "REPLACEMENT": "Remplacement",
   "REPLACEMENT": "Remplacement",
   "TEMPORARY": "Vacataire",
   "TEMPORARY": "Vacataire",
   "INTERIM": "Intermittent",
   "INTERIM": "Intermittent",
-  "OTHER": "Autre"
+  "OTHER": "Autre",
+  "Invalid phone number": "Numéro de téléphone invalide"
 }
 }

+ 1 - 1
package.json

@@ -41,7 +41,7 @@
     "date-fns": "^3.3.1",
     "date-fns": "^3.3.1",
     "eslint-import-resolver-typescript": "^3.6.1",
     "eslint-import-resolver-typescript": "^3.6.1",
     "leaflet": "^1.9.3",
     "leaflet": "^1.9.3",
-    "libphonenumber-js": "^1.10.55",
+    "libphonenumber-js": "^1.12.9",
     "nuxt": "^3.11.2",
     "nuxt": "^3.11.2",
     "nuxt-gtag": "^2.0.6",
     "nuxt-gtag": "^2.0.6",
     "nuxt-lodash": "^2.5.3",
     "nuxt-lodash": "^2.5.3",

+ 105 - 21
pages/shop/try/artist-premium.vue

@@ -70,6 +70,16 @@
               @submit.prevent="submit"
               @submit.prevent="submit"
             >
             >
               <v-container>
               <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
                 <i
                   >Les champs dont le nom est suivi d'un astérisque (*) sont
                   >Les champs dont le nom est suivi d'un astérisque (*) sont
                   obligatoires.</i
                   obligatoires.</i
@@ -390,11 +400,12 @@ import { useRouter } from 'vue-router'
 import type { Ref } from 'vue'
 import type { Ref } from 'vue'
 import { reactive } from 'vue'
 import { reactive } from 'vue'
 import _ from 'lodash'
 import _ from 'lodash'
-import { useRuntimeConfig } from '#app'
+import { parsePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js'
+import { useRuntimeConfig, useAsyncData, useNuxtApp } from '#app'
 import type { TrialRequest } from '~/types/interface'
 import type { TrialRequest } from '~/types/interface'
 import { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
 import { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
-import { slugify } from '~/utils/string'
+import { slugify } from '~/services/utils/stringUtils'
 
 
 const router = useRouter()
 const router = useRouter()
 
 
@@ -418,6 +429,10 @@ const legalStatuses = Object.values(LEGAL_STATUS)
 // Get apiRequestService for subdomain availability check
 // Get apiRequestService for subdomain availability check
 const { ap2iRequestService } = useAp2iRequestService()
 const { ap2iRequestService } = useAp2iRequestService()
 
 
+// Check if we're in a development environment
+const config = useRuntimeConfig()
+const isDevelopment = computed(() => config.public.env === 'dev')
+
 // Trial request data
 // Trial request data
 const trialRequest = reactive<TrialRequest>({
 const trialRequest = reactive<TrialRequest>({
   structureName: '',
   structureName: '',
@@ -440,6 +455,32 @@ const trialRequest = reactive<TrialRequest>({
   newsletterSubscription: false,
   newsletterSubscription: false,
 })
 })
 
 
+// Function to fill the form with dummy data
+const fillWithDummyData = () => {
+  trialRequest.structureName = 'Compagnie Artistique Test'
+  trialRequest.address = '123 Rue des Arts'
+  trialRequest.addressComplement = 'Bâtiment B'
+  trialRequest.postalCode = '75001'
+  trialRequest.city = 'Paris'
+  trialRequest.structureEmail = 'contact@compagnie-test.fr'
+  trialRequest.structureType = 'ARTISTIC_PRACTICE_ONLY'
+  trialRequest.legalStatus = 'ASSOCIATION_LAW_1901'
+  trialRequest.structureIdentifier =
+    'compagnie-test-' + Math.floor(Math.random() * 1000)
+  trialRequest.siren = '123456789'
+  trialRequest.representativeFirstName = 'Jean'
+  trialRequest.representativeLastName = 'Dupont'
+  trialRequest.representativeFunction = 'Directeur Artistique'
+  trialRequest.representativeEmail = 'jean.dupont@compagnie-test.fr'
+  trialRequest.representativePhone = '0612345678'
+  trialRequest.termsAccepted = true
+  trialRequest.legalRepresentative = true
+  trialRequest.newsletterSubscription = true
+
+  // Trigger subdomain availability check
+  checkSubdomainAvailabilityDebounced()
+}
+
 // Track if structure identifier has been manually modified
 // Track if structure identifier has been manually modified
 const structureIdentifierModified = ref(false)
 const structureIdentifierModified = ref(false)
 
 
@@ -493,9 +534,14 @@ const validateEmail = (email: string) =>
 const validatePostalCode = (postalCode: string) =>
 const validatePostalCode = (postalCode: string) =>
   /^\d{5}$/.test(postalCode) || 'Code postal invalide (5 chiffres)'
   /^\d{5}$/.test(postalCode) || 'Code postal invalide (5 chiffres)'
 
 
-const validatePhone = (phone: string) =>
-  /^((\+|00)33\s?|0)[1-9]([\s.-]?\d{2}){4}$/.test(phone) ||
-  'Numéro de téléphone invalide'
+const validatePhone = (phone: string) => {
+  try {
+    // Assume French phone number if no country code is provided
+    return isValidPhoneNumber(phone, 'FR') || 'Numéro de téléphone invalide'
+  } catch (error) {
+    return 'Numéro de téléphone invalide'
+  }
+}
 
 
 const validateSiren = (siren: string) =>
 const validateSiren = (siren: string) =>
   !siren || /^\d{9}$/.test(siren) || 'SIREN invalide (9 chiffres)'
   !siren || /^\d{9}$/.test(siren) || 'SIREN invalide (9 chiffres)'
@@ -526,6 +572,21 @@ const validateSubdomainAvailability = (value: string) => {
 const trialRequestSent: Ref<boolean> = ref(false)
 const trialRequestSent: Ref<boolean> = ref(false)
 const errorMsg: Ref<string | null> = ref(null)
 const errorMsg: Ref<string | null> = ref(null)
 
 
+// Function to convert phone number to international format
+const convertToInternationalFormat = (phone: string): string => {
+  try {
+    // Assume French phone number if no country code is provided
+    const phoneNumber = parsePhoneNumber(phone, 'FR')
+    if (phoneNumber && phoneNumber.isValid()) {
+      return phoneNumber.format('E.164') // E.164 format: +33123456789
+    }
+    return phone
+  } catch (error) {
+    console.error('Error converting phone number:', error)
+    return phone
+  }
+}
+
 // Submit function
 // Submit function
 const submit = async (): Promise<void> => {
 const submit = async (): Promise<void> => {
   const { valid } = await form.value!.validate()
   const { valid } = await form.value!.validate()
@@ -534,25 +595,24 @@ const submit = async (): Promise<void> => {
     return
     return
   }
   }
 
 
+  // Convert phone number to international format before submission
+  trialRequest.representativePhone = convertToInternationalFormat(
+    trialRequest.representativePhone
+  )
+
   try {
   try {
-    const config = useRuntimeConfig()
-    const apiUrl = `${config.public.apiBaseUrl}/trial/artists_premium`
-
-    // Send the data to the API
-    const response = await fetch(apiUrl, {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(trialRequest),
-    })
-
-    if (!response.ok) {
-      throw new Error(`API request failed with status ${response.status}`)
+    const { data, error } = await useAsyncData('submit-trial-request', () =>
+      ap2iRequestService.post(
+        '/api/public/shop/new-structure-artist-premium-trial-request',
+        trialRequest
+      )
+    )
+
+    if (error.value) {
+      throw error.value
     }
     }
 
 
-    const data = await response.json()
-    console.log('Trial request submitted successfully:', data)
+    console.log('Trial request submitted successfully:', data.value)
 
 
     trialRequestSent.value = true
     trialRequestSent.value = true
     errorMsg.value = null
     errorMsg.value = null
@@ -561,8 +621,25 @@ const submit = async (): Promise<void> => {
     setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
     setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
   } catch (e) {
   } catch (e) {
     console.error('Error submitting trial request:', e)
     console.error('Error submitting trial request:', e)
+
     errorMsg.value =
     errorMsg.value =
       "Une erreur s'est produite lors de l'activation de votre essai. Veuillez réessayer plus tard ou nous contacter directement."
       "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)
+      }
+    }
   }
   }
 }
 }
 
 
@@ -698,4 +775,11 @@ h1 {
     }
     }
   }
   }
 }
 }
+
+.dev-tools-container {
+  position: absolute;
+  bottom: 10px;
+  right: 10px;
+  z-index: 10;
+}
 </style>
 </style>

+ 19 - 2
services/data/apiRequestService.ts

@@ -72,22 +72,39 @@ class ApiRequestService {
    * @param url
    * @param url
    * @param body
    * @param body
    * @param query
    * @param query
+   * @param headers
    * @protected
    * @protected
    */
    */
   protected async request(
   protected async request(
     method: HTTP_METHOD,
     method: HTTP_METHOD,
     url: string,
     url: string,
     body: string | AnyJson | null = null,
     body: string | AnyJson | null = null,
-    query: AssociativeArray | null = null
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ): Promise<Response> {
   ): Promise<Response> {
     const config: FetchOptions = { method }
     const config: FetchOptions = { method }
     if (query) {
     if (query) {
       config.query = query
       config.query = query
     }
     }
-    if (method === HTTP_METHOD.POST || method === HTTP_METHOD.PUT) {
+
+    config.headers = headers ? { ...headers } : {}
+
+    config.headers.Accept = 'application/ld+json'
+    config.headers['Content-Type'] = 'application/ld+json'
+
+    if (
+      method === HTTP_METHOD.POST ||
+      method === HTTP_METHOD.PUT ||
+      method === HTTP_METHOD.PATCH
+    ) {
       config.body = body
       config.body = body
     }
     }
 
 
+    if (method === HTTP_METHOD.PATCH) {
+      // config.headers['Accept'] = 'application/merge-patch+json'
+      config.headers['Content-Type'] = 'application/merge-patch+json'
+    }
+
     // @ts-expect-error TODO: solve the type mismatch
     // @ts-expect-error TODO: solve the type mismatch
     return await this.fetch(url, config)
     return await this.fetch(url, config)
   }
   }

+ 0 - 0
utils/string.ts → services/utils/stringUtils.ts


+ 1 - 0
types/enum/data.ts

@@ -2,6 +2,7 @@ export const enum HTTP_METHOD {
   POST = 'POST',
   POST = 'POST',
   PUT = 'PUT',
   PUT = 'PUT',
   GET = 'GET',
   GET = 'GET',
+  PATCH = 'PATCH',
   DELETE = 'DELETE',
   DELETE = 'DELETE',
 }
 }
 
 

+ 5 - 5
yarn.lock

@@ -8564,10 +8564,10 @@ __metadata:
   languageName: node
   languageName: node
   linkType: hard
   linkType: hard
 
 
-"libphonenumber-js@npm:^1.10.55":
-  version: 1.11.4
-  resolution: "libphonenumber-js@npm:1.11.4"
-  checksum: 10c0/0a606da67b4b465e6e157570ad5e70b92f59197cdc1c505d160422a21a894b55a75c9044b863d0eaf4d96884d3fa3e77268adf55afc2d8f11efae7f7a249e7cc
+"libphonenumber-js@npm:^1.12.9":
+  version: 1.12.9
+  resolution: "libphonenumber-js@npm:1.12.9"
+  checksum: 10c0/77fb86802cdf339f472e6a65394603fec0dabe76faca8b5efb4ebcfada2c11a7b39b6773de400e8ec6528354d4b49317797673b14e921117cadbe6b76f01613d
   languageName: node
   languageName: node
   linkType: hard
   linkType: hard
 
 
@@ -11446,7 +11446,7 @@ __metadata:
     eslint-plugin-vue: "npm:^9.21.1"
     eslint-plugin-vue: "npm:^9.21.1"
     jsdom: "npm:^24.0.0"
     jsdom: "npm:^24.0.0"
     leaflet: "npm:^1.9.3"
     leaflet: "npm:^1.9.3"
-    libphonenumber-js: "npm:^1.10.55"
+    libphonenumber-js: "npm:^1.12.9"
     nuxt: "npm:^3.11.2"
     nuxt: "npm:^3.11.2"
     nuxt-gtag: "npm:^2.0.6"
     nuxt-gtag: "npm:^2.0.6"
     nuxt-lodash: "npm:^2.5.3"
     nuxt-lodash: "npm:^2.5.3"