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
 NUXT_ENV=test
+NUXT_PUBLIC_ENV=test
 NUXT_DEBUG=1
 DEBUG=1
 

+ 1 - 0
env/.env.docker

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

+ 1 - 0
env/.env.prod

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

+ 1 - 0
env/.env.test

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

+ 1 - 0
env/.env.test1

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

+ 1 - 0
env/.env.test2

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

+ 1 - 0
env/.env.test3

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

+ 1 - 0
env/.env.test4

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

+ 1 - 0
env/.env.test5

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

+ 1 - 0
env/.env.test6

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

+ 1 - 0
env/.env.test7

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

+ 1 - 0
env/.env.test8

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

+ 1 - 0
env/.env.test9

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

+ 2 - 1
lang/fr.json

@@ -6,5 +6,6 @@
   "REPLACEMENT": "Remplacement",
   "TEMPORARY": "Vacataire",
   "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",
     "eslint-import-resolver-typescript": "^3.6.1",
     "leaflet": "^1.9.3",
-    "libphonenumber-js": "^1.10.55",
+    "libphonenumber-js": "^1.12.9",
     "nuxt": "^3.11.2",
     "nuxt-gtag": "^2.0.6",
     "nuxt-lodash": "^2.5.3",

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

@@ -70,6 +70,16 @@
               @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
@@ -390,11 +400,12 @@ import { useRouter } from 'vue-router'
 import type { Ref } from 'vue'
 import { reactive } from 'vue'
 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 { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
-import { slugify } from '~/utils/string'
+import { slugify } from '~/services/utils/stringUtils'
 
 const router = useRouter()
 
@@ -418,6 +429,10 @@ const legalStatuses = Object.values(LEGAL_STATUS)
 // Get apiRequestService for subdomain availability check
 const { ap2iRequestService } = useAp2iRequestService()
 
+// Check if we're in a development environment
+const config = useRuntimeConfig()
+const isDevelopment = computed(() => config.public.env === 'dev')
+
 // Trial request data
 const trialRequest = reactive<TrialRequest>({
   structureName: '',
@@ -440,6 +455,32 @@ const trialRequest = reactive<TrialRequest>({
   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
 const structureIdentifierModified = ref(false)
 
@@ -493,9 +534,14 @@ const validateEmail = (email: string) =>
 const validatePostalCode = (postalCode: string) =>
   /^\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) =>
   !siren || /^\d{9}$/.test(siren) || 'SIREN invalide (9 chiffres)'
@@ -526,6 +572,21 @@ const validateSubdomainAvailability = (value: string) => {
 const trialRequestSent: Ref<boolean> = ref(false)
 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
 const submit = async (): Promise<void> => {
   const { valid } = await form.value!.validate()
@@ -534,25 +595,24 @@ const submit = async (): Promise<void> => {
     return
   }
 
+  // Convert phone number to international format before submission
+  trialRequest.representativePhone = convertToInternationalFormat(
+    trialRequest.representativePhone
+  )
+
   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
     errorMsg.value = null
@@ -561,8 +621,25 @@ const submit = async (): Promise<void> => {
     setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
   } catch (e) {
     console.error('Error submitting trial request:', e)
+
     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)
+      }
+    }
   }
 }
 
@@ -698,4 +775,11 @@ h1 {
     }
   }
 }
+
+.dev-tools-container {
+  position: absolute;
+  bottom: 10px;
+  right: 10px;
+  z-index: 10;
+}
 </style>

+ 19 - 2
services/data/apiRequestService.ts

@@ -72,22 +72,39 @@ class ApiRequestService {
    * @param url
    * @param body
    * @param query
+   * @param headers
    * @protected
    */
   protected async request(
     method: HTTP_METHOD,
     url: string,
     body: string | AnyJson | null = null,
-    query: AssociativeArray | null = null
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ): Promise<Response> {
     const config: FetchOptions = { method }
     if (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
     }
 
+    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
     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',
   PUT = 'PUT',
   GET = 'GET',
+  PATCH = 'PATCH',
   DELETE = 'DELETE',
 }
 

+ 5 - 5
yarn.lock

@@ -8564,10 +8564,10 @@ __metadata:
   languageName: node
   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
   linkType: hard
 
@@ -11446,7 +11446,7 @@ __metadata:
     eslint-plugin-vue: "npm:^9.21.1"
     jsdom: "npm:^24.0.0"
     leaflet: "npm:^1.9.3"
-    libphonenumber-js: "npm:^1.10.55"
+    libphonenumber-js: "npm:^1.12.9"
     nuxt: "npm:^3.11.2"
     nuxt-gtag: "npm:^2.0.6"
     nuxt-lodash: "npm:^2.5.3"