artist-premium.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. <template>
  2. <div class="theme-artist">
  3. <CommonMeta
  4. title="Essai gratuit Opentalent Artist Premium - 30 jours sans engagement"
  5. description="Essayez gratuitement Opentalent Artist Premium pendant 30 jours. Solution complète pour orchestres, chorales, compagnies de théâtre, de danse ou de cirque."
  6. />
  7. <div class="background-container">
  8. <LayoutContainer class="trial-container">
  9. <div id="anchor" />
  10. <v-card class="form-card">
  11. <v-card-text>
  12. <h1 class="text-center mb-6">
  13. Essayez gratuitement Opentalent Artist Premium pendant 30 jours !
  14. </h1>
  15. <div v-if="!trialRequestSent">
  16. <div class="description mb-8">
  17. <p>
  18. Opentalent Artist Premium est une solution en ligne complète,
  19. pensée pour les orchestres, chorales, compagnies de théâtre,
  20. de danse ou de cirque. Elle vous aide à gagner du temps dans
  21. l'organisation de vos activités, à mieux collaborer avec vos
  22. équipes et à renforcer votre visibilité auprès de votre
  23. public.
  24. </p>
  25. <p>
  26. Pendant 30 jours, profitez de toutes les fonctionnalités
  27. d'Opentalent Artist Premium, gratuitement et sans engagement :
  28. </p>
  29. <ul class="benefits-list">
  30. <li>
  31. <span class="mr-1">✔️</span> Gestion intuitive des membres
  32. et des événements
  33. </li>
  34. <li>
  35. <span class="mr-1">✔️</span> Planification avancée des
  36. répétitions, spectacles et tournées
  37. </li>
  38. <li>
  39. <span class="mr-1">✔️</span> Outils de communication
  40. intégrés (emails, publipostage, etc.)
  41. </li>
  42. <li>
  43. <span class="mr-1">✔️</span> Site web personnalisable pour
  44. présenter vos projets et votre structure
  45. </li>
  46. <li>
  47. <span class="mr-1">✔️</span> Accès collaboratif pour vos
  48. équipes, en temps réel
  49. </li>
  50. </ul>
  51. <p>
  52. Il vous suffit de remplir le formulaire ci-dessous pour
  53. activer votre essai gratuit.
  54. </p>
  55. <p>
  56. Lancez-vous dès aujourd'hui et découvrez comment Opentalent
  57. peut transformer votre organisation artistique !
  58. </p>
  59. </div>
  60. <v-alert
  61. type="info"
  62. variant="tonal"
  63. border="start"
  64. class="mb-4"
  65. density="comfortable"
  66. title="Vous êtes adhérents à la Confédération Musicale de France ? Et si on vous disait que vous l'aviez déjà..."
  67. >
  68. Dans le cadre de votre adhésion, vous bénéficiez de la version
  69. Opentalent Artist Standard, et de conditions privilégiées pour
  70. la version Artist Premium. Contactez votre fédération pour
  71. obtenir vos codes d'accès.
  72. <div class="mt-2">
  73. <a href="https://www.cmf-musique.org/contact/" target="_blank"
  74. >Je souhaite obtenir mon code d'accès</a
  75. >
  76. </div>
  77. </v-alert>
  78. <v-form
  79. ref="form"
  80. validate-on="submit lazy"
  81. @submit.prevent="submit"
  82. >
  83. <v-container>
  84. <div v-if="isDevelopmentOrTest" class="dev-tools-container">
  85. <v-btn
  86. color="info"
  87. size="small"
  88. prepend-icon="fa fa-magic"
  89. @click="fillWithDummyData"
  90. >
  91. Remplir avec des données de test
  92. </v-btn>
  93. </div>
  94. <i
  95. >Les champs dont le nom est suivi d'un astérisque (*) sont
  96. obligatoires.</i
  97. >
  98. <h2 class="section-title">Coordonnées de la structure</h2>
  99. <!-- Structure name -->
  100. <v-row>
  101. <v-col cols="12">
  102. <v-text-field
  103. v-model="trialRequest.structureName"
  104. :rules="[validateRequired]"
  105. label="Nom de la structure*"
  106. required
  107. @input="onStructureNameUpdated"
  108. />
  109. </v-col>
  110. </v-row>
  111. <!-- Structure address -->
  112. <v-row>
  113. <v-col cols="12" md="6">
  114. <v-text-field
  115. v-model="trialRequest.address"
  116. :rules="[validateRequired]"
  117. label="Adresse du siège social de la structure*"
  118. required
  119. />
  120. </v-col>
  121. <v-col cols="12" md="6">
  122. <v-text-field
  123. v-model="trialRequest.addressComplement"
  124. label="Adresse (suite)"
  125. />
  126. </v-col>
  127. </v-row>
  128. <v-row>
  129. <v-col cols="12" md="6">
  130. <v-text-field
  131. v-model="trialRequest.postalCode"
  132. :rules="[validateRequired, validatePostalCode]"
  133. label="Code postal*"
  134. required
  135. />
  136. </v-col>
  137. <v-col cols="12" md="6">
  138. <v-text-field
  139. v-model="trialRequest.city"
  140. :rules="[validateRequired]"
  141. label="Ville*"
  142. required
  143. />
  144. </v-col>
  145. </v-row>
  146. <!-- Structure email and SIREN -->
  147. <v-row>
  148. <v-col cols="12" md="6">
  149. <v-text-field
  150. v-model="trialRequest.structureEmail"
  151. :rules="[validateRequired, validateEmail]"
  152. label="Adresse email de la structure*"
  153. required
  154. type="email"
  155. />
  156. </v-col>
  157. <v-col cols="12" md="6">
  158. <v-text-field
  159. v-model="trialRequest.siren"
  160. :rules="[validateSiren]"
  161. label="SIREN (optionnel)"
  162. hint="Numéro à 9 chiffres"
  163. />
  164. </v-col>
  165. </v-row>
  166. <!-- Structure type and legal status -->
  167. <v-row>
  168. <v-col cols="12" md="6">
  169. <v-select
  170. v-model="trialRequest.structureType"
  171. :rules="[validateRequired]"
  172. label="Type de la structure*"
  173. :items="structureTypes"
  174. item-value="value"
  175. item-title="title"
  176. required
  177. />
  178. </v-col>
  179. <v-col cols="12" md="6">
  180. <v-select
  181. v-model="trialRequest.legalStatus"
  182. :rules="[validateRequired]"
  183. label="Statut juridique*"
  184. :items="legalStatuses"
  185. item-value="value"
  186. item-title="title"
  187. required
  188. />
  189. </v-col>
  190. </v-row>
  191. <h2 class="section-title">Représentée par</h2>
  192. <!-- Representative function -->
  193. <v-row>
  194. <v-col cols="12">
  195. <v-text-field
  196. v-model="trialRequest.representativeFunction"
  197. :rules="[validateRequired]"
  198. label="Fonction*"
  199. required
  200. />
  201. </v-col>
  202. </v-row>
  203. <!-- Representative name -->
  204. <v-row>
  205. <v-col cols="12" md="6">
  206. <v-text-field
  207. v-model="trialRequest.representativeFirstName"
  208. :rules="[validateRequired]"
  209. label="Prénom*"
  210. required
  211. />
  212. </v-col>
  213. <v-col cols="12" md="6">
  214. <v-text-field
  215. v-model="trialRequest.representativeLastName"
  216. :rules="[validateRequired]"
  217. label="Nom*"
  218. required
  219. />
  220. </v-col>
  221. </v-row>
  222. <!-- Representative contact -->
  223. <v-row>
  224. <v-col cols="12" md="6">
  225. <v-text-field
  226. v-model="trialRequest.representativeEmail"
  227. :rules="[validateRequired, validateEmail]"
  228. label="Adresse email*"
  229. required
  230. type="email"
  231. />
  232. </v-col>
  233. <v-col cols="12" md="6">
  234. <CommonPhoneInput
  235. ref="phoneInput"
  236. v-model="trialRequest.representativePhone"
  237. label="Téléphone*"
  238. required
  239. />
  240. </v-col>
  241. </v-row>
  242. <h2 class="section-title mb-6">Informations de connexion</h2>
  243. <!-- Structure identifier -->
  244. <v-row>
  245. <v-col cols="12" md="6" class="mx-auto">
  246. <v-text-field
  247. v-model="trialRequest.structureIdentifier"
  248. :rules="[
  249. validateRequired,
  250. validateSubdomain,
  251. validateSubdomainAvailability,
  252. ]"
  253. label="Identifiant de la structure*"
  254. required
  255. class="text-center"
  256. @input="onStructureIdentifierUpdated"
  257. />
  258. <div class="validationMessage">
  259. <span v-if="validationPending">
  260. <v-progress-circular size="16" indeterminate />
  261. <i class="ml-2">Vérification en cours</i>
  262. </span>
  263. <span
  264. v-else-if="subdomainAvailable === true"
  265. class="text-success"
  266. >
  267. <v-icon>fa fa-check</v-icon>
  268. <i class="ml-2"> Cet identifiant est disponible</i>
  269. </span>
  270. <span
  271. v-else-if="subdomainAvailable === false"
  272. class="text-error"
  273. >
  274. <v-icon>fa fa-x</v-icon>
  275. <i class="ml-2"
  276. >Cet identifiant n'est pas disponible</i
  277. >
  278. </span>
  279. </div>
  280. <div class="mt-2">
  281. <i v-if="trialRequest.structureIdentifier">
  282. Le compte administrateur de la structure sera
  283. <strong>
  284. admin{{ trialRequest.structureIdentifier }}
  285. </strong>
  286. </i>
  287. </div>
  288. <div>
  289. <i>
  290. Veuillez renseigner un mot de passe pour ce compte :
  291. </i>
  292. </div>
  293. </v-col>
  294. </v-row>
  295. <!-- Password field -->
  296. <v-row>
  297. <v-col cols="12" md="6" class="mx-auto">
  298. <v-text-field
  299. v-model="trialRequest.password"
  300. :rules="[validateRequired, validatePassword]"
  301. label="Mot de passe*"
  302. required
  303. :type="showPassword ? 'text' : 'password'"
  304. :append-inner-icon="
  305. showPassword ? 'fa fa-eye-slash' : 'fa fa-eye'
  306. "
  307. @click:append-inner="showPassword = !showPassword"
  308. />
  309. <div class="mt-1">
  310. <i>
  311. Le mot de passe doit contenir au moins 8 caractères,
  312. une minuscule, une majuscule, un chiffre et un
  313. caractère spécial.
  314. </i>
  315. </div>
  316. <v-text-field
  317. v-model="trialRequest.confirmPassword"
  318. :rules="[
  319. showPassword ? () => true : validateRequired,
  320. showPassword ? () => true : validatePasswordMatch,
  321. ]"
  322. label="Confirmer le mot de passe*"
  323. :required="!showPassword"
  324. :disabled="showPassword"
  325. :type="showPassword ? 'text' : 'password'"
  326. />
  327. </v-col>
  328. </v-row>
  329. <h2 class="section-title">Accord de termes et conditions</h2>
  330. <!-- Terms checkboxes -->
  331. <v-checkbox
  332. v-model="trialRequest.termsAccepted"
  333. :rules="[validateCheckbox]"
  334. required
  335. >
  336. <template #label>
  337. Mon organisme accepte les &nbsp;
  338. <a
  339. href="https://maestro.opentalent.fr/uploads/share/Documents_juridique/CGU.pdf"
  340. target="_blank"
  341. >
  342. conditions générales d'utilisation </a
  343. >.*
  344. </template>
  345. </v-checkbox>
  346. <v-checkbox
  347. v-model="trialRequest.legalRepresentative"
  348. :rules="[validateCheckbox]"
  349. label="J'agis en tant que représentant légal de l'association ou de la structure.*"
  350. required
  351. />
  352. <v-checkbox
  353. v-model="trialRequest.newsletterSubscription"
  354. label="J'accepte de recevoir la lettre d'information culturelle afin de découvrir des idées de sorties adaptées à ma région."
  355. />
  356. <div class="d-flex flex-row justify-center">
  357. <LayoutCaptcha />
  358. </div>
  359. <!-- Submit Button -->
  360. <div class="d-flex flex-row justify-center my-10">
  361. <v-btn
  362. type="submit"
  363. color="secondary"
  364. size="large"
  365. class="submit-btn"
  366. >
  367. COMMENCER MON ESSAI DE 30 JOURS
  368. </v-btn>
  369. </div>
  370. <p class="text-center no-credit-card">
  371. Aucune carte de crédit requise. En cliquant sur "Commencer
  372. mon essai de 30 jours", vous acceptez de démarrer votre
  373. période d'essai gratuit.
  374. </p>
  375. <div v-if="validationError" class="error">
  376. Des champs du formulaire sont invalides ou manquants.
  377. </div>
  378. <div v-if="errorMsg" class="error">
  379. {{ errorMsg }}
  380. </div>
  381. </v-container>
  382. <div class="legal">
  383. Les données recueillies par Opentalent sont utilisées pour le
  384. traitement de votre demande et pour vous informer sur nos
  385. offres. Elles sont destinées aux services Opentalent et à ses
  386. sous-traitants pour l'exécution des contrats. Conformément à
  387. la loi "Informatique et Libertés du 6 Janvier 1978", vous
  388. disposez d'un droit d'accès, de modifications, de
  389. rectification et de suppression des données vous concernant.
  390. Pour toute demande, adressez-vous à : OPENTALENT, 265 rue de
  391. la Grange 74950 SCIONZIER - FRANCE, opentalent.fr s'engage à
  392. la confidentialité et à la protection de vos données."
  393. </div>
  394. </v-form>
  395. </div>
  396. <div
  397. v-else
  398. class="confirmation-message d-flex flex-row justify-center"
  399. >
  400. <v-card>
  401. <v-card-title class="text-center">
  402. <v-icon
  403. icon="fas fa-check mr-2"
  404. color="success"
  405. max-height="48"
  406. />
  407. Félicitations !
  408. </v-card-title>
  409. <v-card-text class="text-center">
  410. <p>
  411. Votre demande d'essai gratuit de 30 jours d'Opentalent
  412. Artist Premium a bien été enregistrée, mais nécessite une
  413. validation de votre part.
  414. </p>
  415. <p>
  416. Vous allez recevoir un email permettant de valider cette
  417. demande, à la suite de quoi votre compte sera créé.
  418. Attention, la durée de validité du lien d'activation est de
  419. 15 minutes.
  420. </p>
  421. <p>
  422. Notre équipe reste à votre disposition pour vous accompagner
  423. durant cette période d'essai.
  424. </p>
  425. </v-card-text>
  426. <v-card-actions class="justify-center">
  427. <v-btn
  428. variant="elevated"
  429. prepend-icon="fas fa-arrow-left"
  430. color="secondary"
  431. to="/opentalent-artist"
  432. >
  433. Retour à la page Opentalent Artist
  434. </v-btn>
  435. </v-card-actions>
  436. </v-card>
  437. </div>
  438. </v-card-text>
  439. </v-card>
  440. </LayoutContainer>
  441. </div>
  442. </div>
  443. </template>
  444. <script setup lang="ts">
  445. import { useRouter } from 'vue-router'
  446. import type { Ref } from 'vue'
  447. import { reactive } from 'vue'
  448. import _ from 'lodash'
  449. import { useRuntimeConfig, useAsyncData } from '#app'
  450. import type { TrialRequest } from '~/types/interface'
  451. import { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
  452. import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
  453. import { useAp2iErrorHandler } from '~/composables/utils/useAp2iErrorHandler'
  454. import {
  455. convertPhoneNumberToInternationalFormat,
  456. slugify,
  457. } from '~/services/utils/stringUtils'
  458. const router = useRouter()
  459. const form: Ref<HTMLElement | null> = ref(null)
  460. // Structure types and legal statuses
  461. const structureTypes = Object.values(STRUCTURE_TYPE)
  462. .map((item) => ({
  463. value: item.key,
  464. title: item.label,
  465. }))
  466. .sort((a, b) => (a.title > b.title ? 1 : -1))
  467. const legalStatuses = Object.values(LEGAL_STATUS)
  468. .map((item) => ({
  469. value: item.key,
  470. title: item.label,
  471. }))
  472. .sort((a, b) => (a.title > b.title ? 1 : -1))
  473. // Get apiRequestService for subdomain availability check
  474. const { ap2iRequestService } = useAp2iRequestService()
  475. const { processApiError } = useAp2iErrorHandler()
  476. // Check if we're in a development environment
  477. const config = useRuntimeConfig()
  478. const isDevelopmentOrTest = computed(
  479. () => config.public.env === 'dev' || config.public.env === 'test'
  480. )
  481. // Trial request data
  482. const trialRequest = reactive<TrialRequest>({
  483. structureName: '',
  484. address: '',
  485. addressComplement: '',
  486. postalCode: '',
  487. city: '',
  488. structureEmail: '',
  489. structureType: 'ARTISTIC_PRACTICE_ONLY',
  490. legalStatus: 'ASSOCIATION_LAW_1901',
  491. structureIdentifier: '',
  492. siren: '',
  493. representativeFirstName: '',
  494. representativeLastName: '',
  495. representativeFunction: '',
  496. representativeEmail: '',
  497. representativePhone: '',
  498. password: '',
  499. confirmPassword: '',
  500. termsAccepted: false,
  501. legalRepresentative: false,
  502. newsletterSubscription: false,
  503. createWebsite: false,
  504. })
  505. // Function to fill the form with dummy data
  506. const fillWithDummyData = () => {
  507. // Generate a short timestamp (unix timestamp in seconds)
  508. const shortTimestamp = Math.floor(Date.now() / 1000).toString()
  509. trialRequest.structureName = `Compagnie Artistique Test ${shortTimestamp}`
  510. trialRequest.address = '123 Rue des Arts'
  511. trialRequest.addressComplement = 'Bâtiment B'
  512. trialRequest.postalCode = '75001'
  513. trialRequest.city = 'Paris'
  514. trialRequest.structureEmail = 'contact@compagnie-test.fr'
  515. trialRequest.structureType = 'ARTISTIC_PRACTICE_ONLY'
  516. trialRequest.legalStatus = 'ASSOCIATION_LAW_1901'
  517. trialRequest.structureIdentifier = `compagnie-test-${shortTimestamp}`
  518. trialRequest.siren = '123456789'
  519. trialRequest.representativeFirstName = 'Jean'
  520. trialRequest.representativeLastName = 'Dupont'
  521. trialRequest.representativeFunction = 'Directeur Artistique'
  522. trialRequest.representativeEmail = 'jean.dupont@compagnie-test.fr'
  523. trialRequest.representativePhone = '0612345678'
  524. trialRequest.password = 'Test1234!'
  525. trialRequest.confirmPassword = 'Test1234!'
  526. trialRequest.termsAccepted = true
  527. trialRequest.legalRepresentative = true
  528. trialRequest.newsletterSubscription = true
  529. // Trigger subdomain availability check
  530. checkSubdomainAvailabilityDebounced()
  531. }
  532. // Track if structure identifier has been manually modified
  533. const structureIdentifierModified = ref(false)
  534. // Variables for subdomain validation
  535. const validationPending = ref(false)
  536. const subdomainAvailable = ref<boolean | null>(null)
  537. const checkSubdomainAvailability = async (subdomain: string) => {
  538. if (!subdomain || validateSubdomain(subdomain) !== true) {
  539. subdomainAvailable.value = null
  540. return false
  541. }
  542. validationPending.value = true
  543. try {
  544. const subdomainAvailability = await ap2iRequestService.get(
  545. '/api/public/subdomains/is_available',
  546. { subdomain }
  547. )
  548. subdomainAvailable.value =
  549. subdomainAvailability && subdomainAvailability.available === true
  550. validationPending.value = false
  551. return subdomainAvailable.value
  552. } catch (error) {
  553. console.error('Error checking subdomain availability:', error)
  554. subdomainAvailable.value = false
  555. validationPending.value = false
  556. return false
  557. }
  558. }
  559. /**
  560. * Version debounced de la fonction checkAvailability
  561. * @see https://docs-lodash.com/v4/debounce/
  562. */
  563. const checkSubdomainAvailabilityDebounced: _.DebouncedFunc<() => void> =
  564. _.debounce(
  565. async () =>
  566. await checkSubdomainAvailability(trialRequest.structureIdentifier),
  567. 600
  568. )
  569. // Validation rules
  570. const validateRequired = (value: string) =>
  571. !!value || 'Ce champ est obligatoire'
  572. const validateEmail = (email: string) =>
  573. /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || 'Adresse email invalide'
  574. const validatePostalCode = (postalCode: string) =>
  575. /^\d{5}$/.test(postalCode) || 'Code postal invalide (5 chiffres)'
  576. const validateSiren = (siren: string) =>
  577. !siren || /^\d{9}$/.test(siren) || 'SIREN invalide (9 chiffres)'
  578. const validateCheckbox = (value: boolean) =>
  579. value || 'Vous devez accepter cette condition'
  580. const validatePassword = (password: string) => {
  581. if (!password) return 'Ce champ est obligatoire'
  582. const minLength = password.length >= 8
  583. const hasLowercase = /[a-z]/.test(password)
  584. const hasUppercase = /[A-Z]/.test(password)
  585. const hasNumber = /[0-9]/.test(password)
  586. const hasSpecial = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)
  587. if (!minLength) {
  588. return 'Le mot de passe doit contenir au moins 8 caractères'
  589. }
  590. if (!hasLowercase) {
  591. return 'Le mot de passe doit contenir au moins une lettre minuscule'
  592. }
  593. if (!hasUppercase) {
  594. return 'Le mot de passe doit contenir au moins une lettre majuscule'
  595. }
  596. if (!hasNumber) {
  597. return 'Le mot de passe doit contenir au moins un chiffre'
  598. }
  599. if (!hasSpecial) {
  600. return 'Le mot de passe doit contenir au moins un caractère spécial'
  601. }
  602. return true
  603. }
  604. const validatePasswordMatch = () => {
  605. if (!trialRequest.confirmPassword) return 'Ce champ est obligatoire'
  606. return (
  607. trialRequest.password === trialRequest.confirmPassword ||
  608. 'Les mots de passe ne correspondent pas'
  609. )
  610. }
  611. const validateSubdomain = (value: string) => {
  612. if (!value) return 'Ce champ est obligatoire'
  613. const regex = /^[a-z0-9][a-z0-9-]{0,28}[a-z0-9]$/
  614. return (
  615. regex.test(value) ||
  616. 'Format invalide. Utilisez uniquement des lettres minuscules, des chiffres et des tirets. Doit commencer et finir par une lettre ou un chiffre. Maximum 30 caractères.'
  617. )
  618. }
  619. const validateSubdomainAvailability = (value: string) => {
  620. if (!value) return ''
  621. return (
  622. subdomainAvailable.value === true || "Cet identifiant n'est pas disponible"
  623. )
  624. }
  625. // Password visibility toggle
  626. const showPassword = ref(false)
  627. // Form state
  628. const trialRequestSent: Ref<boolean> = ref(false)
  629. const errorMsg: Ref<string | null> = ref(null)
  630. const validationError: Ref<boolean> = ref(false)
  631. // Reference to the phone input component
  632. const phoneInput = ref(null)
  633. // Submit function
  634. const submit = async (): Promise<void> => {
  635. const { valid } = await form.value!.validate()
  636. if (!valid) {
  637. validationError.value = true
  638. return
  639. }
  640. validationError.value = false
  641. // Convert phone number to international format before submission
  642. if (phoneInput.value) {
  643. trialRequest.representativePhone = convertPhoneNumberToInternationalFormat(
  644. trialRequest.representativePhone
  645. )
  646. }
  647. try {
  648. const { data, error } = await useAsyncData('submit-trial-request', () =>
  649. ap2iRequestService.post(
  650. '/api/public/shop/new-structure-artist-premium-trial-request',
  651. trialRequest
  652. )
  653. )
  654. if (error.value) {
  655. throw error.value
  656. }
  657. console.log('Trial request submitted successfully:', data.value)
  658. trialRequestSent.value = true
  659. errorMsg.value = null
  660. // Scroll to top to show confirmation message
  661. setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
  662. } catch (e) {
  663. console.error('Error submitting trial request:', e)
  664. // Process the error message using the common error handler
  665. const processedError = processApiError(e)
  666. // Set the error message directly from the processed error
  667. // The processApiError function now returns either a translated message
  668. // or a generic error message as appropriate
  669. errorMsg.value = processedError
  670. }
  671. }
  672. // Event handler for structureName updates
  673. const onStructureNameUpdated = (newName: string) => {
  674. if (!structureIdentifierModified.value && newName) {
  675. trialRequest.structureIdentifier = slugify(
  676. trialRequest.structureName
  677. ).replace(/[-_]/g, '')
  678. checkSubdomainAvailabilityDebounced()
  679. }
  680. }
  681. // Event handler for structureIdentifier updates
  682. const onStructureIdentifierUpdated = () => {
  683. structureIdentifierModified.value = true
  684. checkSubdomainAvailabilityDebounced()
  685. }
  686. </script>
  687. <style scoped lang="scss">
  688. .background-container {
  689. background-image: url('/images/logos/opentalent/Logo_Opentalent_Griffe.png');
  690. background-size: 700px;
  691. min-height: 100vh;
  692. }
  693. .trial-container {
  694. max-width: 1200px;
  695. margin: 0 auto;
  696. padding: 2rem;
  697. }
  698. .form-card {
  699. box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
  700. max-width: 90%;
  701. margin: 0 auto;
  702. padding: 2rem;
  703. }
  704. h1 {
  705. font-size: 2.5rem;
  706. font-weight: 700;
  707. color: var(--primary-color);
  708. text-decoration: underline var(--artist-color) 3px solid;
  709. margin-bottom: 2rem;
  710. @media (max-width: 768px) {
  711. font-size: 1.8rem;
  712. }
  713. }
  714. .description {
  715. font-size: 1.1rem;
  716. line-height: 1.6;
  717. margin-bottom: 2rem;
  718. p {
  719. margin-bottom: 1rem;
  720. }
  721. }
  722. .benefits-list {
  723. list-style: none;
  724. padding-left: 1rem;
  725. margin: 1.5rem 0;
  726. li {
  727. margin-bottom: 0.5rem;
  728. }
  729. }
  730. .section-title {
  731. margin-top: 2rem;
  732. font-size: 1.5rem;
  733. font-weight: 600;
  734. color: var(--primary-color);
  735. text-decoration: underline var(--artist-color) 3px solid;
  736. margin-bottom: 1rem;
  737. text-transform: uppercase;
  738. letter-spacing: 0.05em;
  739. }
  740. .v-form {
  741. max-width: 1200px;
  742. margin: 0 auto;
  743. .submit-btn {
  744. font-weight: 600;
  745. letter-spacing: 0.05em;
  746. padding: 0 2rem;
  747. }
  748. .error {
  749. color: var(--warning-color);
  750. width: 80%;
  751. margin: 0.7em auto 2em;
  752. text-align: center;
  753. font-size: 1.05rem;
  754. font-weight: 600;
  755. border: 2px solid var(--warning-color);
  756. border-radius: 4px;
  757. padding: 0.5rem;
  758. display: flex;
  759. align-items: center;
  760. justify-content: center;
  761. }
  762. .legal {
  763. opacity: 0.7;
  764. font-size: 14px;
  765. font-style: italic;
  766. margin: 2rem auto;
  767. max-width: 80%;
  768. }
  769. }
  770. .no-credit-card {
  771. font-size: 0.9rem;
  772. opacity: 0.8;
  773. margin-top: 1rem;
  774. }
  775. .confirmation-message {
  776. .v-card {
  777. max-width: 800px;
  778. padding: 2rem;
  779. margin: 4rem 0;
  780. .v-card-title {
  781. font-size: 1.8rem;
  782. font-weight: 700;
  783. color: var(--primary-color);
  784. }
  785. .v-card-text {
  786. font-size: 1.1rem;
  787. line-height: 1.6;
  788. p {
  789. margin-bottom: 1rem;
  790. }
  791. }
  792. }
  793. }
  794. .dev-tools-container {
  795. position: absolute;
  796. bottom: 10px;
  797. right: 10px;
  798. z-index: 10;
  799. }
  800. </style>