artist-premium.vue 30 KB

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