Form.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. <template>
  2. <LayoutContainer>
  3. <div id="anchor" />
  4. <h4><span class="line" /> Veuillez remplir le formulaire ci-dessous</h4>
  5. <v-form
  6. v-if="!contactRequestSent"
  7. ref="form"
  8. validate-on="submit lazy"
  9. @submit.prevent="submit"
  10. >
  11. <v-container>
  12. <i
  13. >Les champs dont le nom est suivi d'un astérisque (*) sont
  14. obligatoires</i
  15. >
  16. <h6>Vos coordonnées</h6>
  17. <!-- Gender selection -->
  18. <v-row>
  19. <v-col cols="12">
  20. <v-radio-group v-model="contactRequest.gender" row mandatory inline>
  21. <v-radio label="Madame" value="Madame" />
  22. <v-radio label="Monsieur" value="Monsieur" />
  23. </v-radio-group>
  24. </v-col>
  25. </v-row>
  26. <!-- Name and Surname -->
  27. <v-row>
  28. <v-col cols="12" md="6">
  29. <v-text-field
  30. v-model="contactRequest.name"
  31. :rules="[validateName]"
  32. label="Nom*"
  33. required
  34. />
  35. </v-col>
  36. <v-col cols="12" md="6">
  37. <v-text-field
  38. v-model="contactRequest.surname"
  39. :rules="[validateSurname]"
  40. label="Prénom*"
  41. required
  42. />
  43. </v-col>
  44. </v-row>
  45. <!-- Postal code and city -->
  46. <v-row>
  47. <v-col cols="12" md="6">
  48. <v-text-field
  49. v-model="contactRequest.postalCode"
  50. label="Code postal*"
  51. :rules="[validatePostalCode]"
  52. />
  53. </v-col>
  54. <v-col cols="12" md="6">
  55. <v-text-field
  56. v-model="contactRequest.city"
  57. label="Ville*"
  58. :rules="[validateCity]"
  59. />
  60. </v-col>
  61. </v-row>
  62. <!-- Email and phone on the same line -->
  63. <v-row>
  64. <v-col cols="12" md="6">
  65. <v-text-field
  66. v-model="contactRequest.email"
  67. :rules="[validateEmail]"
  68. label="Email*"
  69. required
  70. type="email"
  71. />
  72. </v-col>
  73. <v-col cols="12" md="6">
  74. <v-text-field
  75. v-model="contactRequest.phone"
  76. :rules="[validatePhone]"
  77. label="Téléphone*"
  78. type="tel"
  79. />
  80. </v-col>
  81. </v-row>
  82. <!-- Structure name -->
  83. <v-row>
  84. <v-col cols="12">
  85. <v-text-field
  86. v-model="contactRequest.structureName"
  87. :rules="[validateStructureName]"
  88. label="Nom de la structure*"
  89. required
  90. />
  91. </v-col>
  92. </v-row>
  93. <h6>Votre demande concerne *</h6>
  94. <!-- Request type and product concerned -->
  95. <v-row>
  96. <v-col cols="12" md="6">
  97. <v-select
  98. v-model="contactRequest.requestType"
  99. :items="requestTypes"
  100. item-value="id"
  101. item-title="label"
  102. variant="outlined"
  103. />
  104. </v-col>
  105. <v-col cols="12" md="6">
  106. <v-select
  107. v-model="contactRequest.concernedProduct"
  108. label="Produit concerné (facultatif)"
  109. :items="products"
  110. item-value="id"
  111. item-title="label"
  112. variant="outlined"
  113. />
  114. </v-col>
  115. </v-row>
  116. <h6>Votre message</h6>
  117. <!-- Message -->
  118. <v-row class="mb-8">
  119. <v-col cols="12">
  120. <v-textarea
  121. v-model="contactRequest.message"
  122. :rules="[validateNonEmptyMessage, validateMessageLength]"
  123. label="Votre message*"
  124. required
  125. maxlength="400"
  126. />
  127. <span class="remaining-cars-notice"
  128. >{{ leftCars }} caractères restants</span
  129. >
  130. </v-col>
  131. </v-row>
  132. <!-- Policy and checkboxes -->
  133. <v-checkbox
  134. v-model="contactRequest.privacyPolicyAccepted"
  135. :rules="[
  136. (v: boolean) =>
  137. v || 'Vous devez accepter la politique de confidentialité',
  138. ]"
  139. label="J'ai pris connaissance de la politique de confidentialité et j'accepte le traitement de mes données personnelles par Opentalent. *"
  140. />
  141. <v-checkbox
  142. v-model="contactRequest.newsletterSubscription"
  143. label="Je souhaite recevoir des communications d'Opentalent par email (promotions, informations logiciel…). Je pourrai me désinscrire à tout moment."
  144. />
  145. <div class="d-flex flex-row justify-center">
  146. <!-- @see https://github.com/hCaptcha/vue-hcaptcha -->
  147. <LayoutCaptcha />
  148. </div>
  149. <!-- Submit Button -->
  150. <div class="d-flex flex-row justify-center my-10">
  151. <v-btn
  152. type="submit"
  153. variant="outlined"
  154. :height="54"
  155. :width="180"
  156. class="submit-btn"
  157. >
  158. Envoyer
  159. </v-btn>
  160. </div>
  161. <div v-if="errorMsg" class="error">
  162. {{ errorMsg }}
  163. </div>
  164. </v-container>
  165. <div class="legal">
  166. Les données recueillies par Opentalent sont utilisées pour le traitement
  167. de votre demande et pour vous informer sur nos offres. Elles sont
  168. destinées aux services Opentalent et à ses sous-traitants pour
  169. l’exécution des contrats. Conformément à la loi "Informatique et
  170. Libertés du 6 Janvier 1978", vous disposez d’un droit d’accès, de
  171. modifications, de rectification et de suppression des données vous
  172. concernant. Pour toute demande, adressez-vous à : Opentalent, 217 rue
  173. Raoul Follereau, 74300 CLUSES, opentalent.fr s’engage à la
  174. confidentialité et à la protection de vos données.
  175. </div>
  176. </v-form>
  177. <div v-else class="confirmation-message d-flex flex-row justify-center">
  178. <v-card>
  179. <v-icon icon="fas fa-check mr-1" />
  180. Votre demande de contact a bien été enregistrée, nous reviendrons vers
  181. vous dès que possible.
  182. </v-card>
  183. </div>
  184. </LayoutContainer>
  185. </template>
  186. <script setup lang="ts">
  187. import { useRouter } from 'vue-router'
  188. import type { ComputedRef, Ref } from 'vue'
  189. import type { reactive } from 'vue'
  190. import ContactRequest from '~/models/Maestro/ContactRequest'
  191. import { useEntityManager } from '~/composables/data/useEntityManager'
  192. const route = useRoute()
  193. const router = useRouter()
  194. const { em } = useEntityManager()
  195. const form: Ref<HTMLElement | null> = ref(null)
  196. const requestTypes: Array<{ id: string; label: string }> = [
  197. { id: 'CONTACT_REQUEST_INFORMATION', label: "Demande d'information" },
  198. { id: 'CONTACT_REQUEST_ESTIMATE', label: 'Demande de devis' },
  199. { id: 'CONTACT_REQUEST_DEMO', label: 'Demande de démonstration' },
  200. { id: 'CONTACT_REQUEST_OPTION', label: "Demande d'option supplémentaire" },
  201. { id: 'CONTACT_REQUEST_OTHER', label: 'Autre' },
  202. ]
  203. const products: Array<{ id: string; label: string }> = [
  204. { id: 'PRODUCT_AGENDA', label: 'Agenda culturel' },
  205. { id: 'PRODUCT_ARTIST', label: 'Opentalent Artist' },
  206. { id: 'PRODUCT_SCHOOL', label: 'Opentalent School' },
  207. { id: 'PRODUCT_MANAGER', label: 'Opentalent Manager' },
  208. { id: 'PRODUCT_ADVERTISING', label: 'Publicité' },
  209. { id: 'PRODUCT_OTHER', label: 'Autre' },
  210. ]
  211. const defaultRequestType = route.query.request ?? 'CONTACT_REQUEST_INFORMATION'
  212. // @ts-ignore
  213. const contactRequest: ContactRequest = reactive(
  214. em.newInstance(ContactRequest, { requestType: defaultRequestType })
  215. )
  216. // --- Validation ---
  217. const maxMessageLength = 2000
  218. const leftCars: ComputedRef<number> = computed(
  219. () =>
  220. maxMessageLength -
  221. (contactRequest.message ? contactRequest.message.length : 0)
  222. )
  223. const validateName = (name: string | null) => !!name || 'Le nom est obligatoire'
  224. const validateSurname = (surname: string | null) =>
  225. !!surname || 'Le prénom est obligatoire'
  226. const validatePostalCode = (postalCode: string | null) =>
  227. (!!postalCode && /^\d{5}$/.test(postalCode)) ||
  228. 'Le code postal doit être valide'
  229. const validateCity = (city: string | null) =>
  230. !!city || 'La ville est obligatoire'
  231. const validateEmail = (email: string | null) =>
  232. (!!email && /.+@.+\..+/.test(email)) || "L'adresse e-mail doit être valide"
  233. const validatePhone = (email: string | null) =>
  234. (!!email && /^((\+|00)33\s?|0)[1-7]([\s.]?\d{2}){4}$/.test(email)) ||
  235. 'Le numéro de téléphone doit être valide'
  236. const validateStructureName = (structureName: string | null) =>
  237. !!structureName || 'Le nom de la structure est requis'
  238. const validateNonEmptyMessage = (message: string | null) =>
  239. (!!message && message.length > 0) || 'Le message ne peut pas être vide'
  240. const validateMessageLength = (message: string | null) =>
  241. (!!message && message.length <= maxMessageLength) ||
  242. 'Le message ne doit pas dépasser ' + maxMessageLength + ' caractères'
  243. const contactRequestSent: Ref<boolean> = ref(false)
  244. const errorMsg: Ref<string | null> = ref(null)
  245. /**
  246. * Submits the contact form.
  247. *
  248. * This function validates the form and sets the value of a variable to indicate whether the form submission was successful.
  249. *
  250. * @function
  251. *
  252. * @returns {void}
  253. */
  254. const submit = async (): Promise<void> => {
  255. const { valid } = await form.value.validate()
  256. if (!valid) {
  257. contactRequestSent.value = false
  258. return
  259. }
  260. try {
  261. await em.persist(ContactRequest, contactRequest)
  262. } catch (e) {
  263. errorMsg.value =
  264. "Une erreur s'est produite, nous sommes navrés pour le désagrément. Veuillez réessayer plus tard."
  265. return
  266. }
  267. contactRequestSent.value = true
  268. errorMsg.value = null
  269. // Défile vers le début de page pour afficher le message de confirmation
  270. setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
  271. }
  272. </script>
  273. <style scoped lang="scss">
  274. h4 {
  275. display: flex;
  276. flex-direction: row;
  277. font-size: 40px;
  278. line-height: 95px;
  279. margin-bottom: 1rem;
  280. align-items: center;
  281. font-weight: 100;
  282. @media (max-width: 600px) {
  283. font-size: 24px;
  284. line-height: 48px;
  285. }
  286. .line {
  287. display: block;
  288. height: 1px;
  289. width: 64px;
  290. min-width: 64px;
  291. border-top: solid 1px var(--on-neutral-color);
  292. margin-right: 18px;
  293. }
  294. }
  295. .v-form {
  296. max-width: 1400px;
  297. margin: 0 auto;
  298. h6 {
  299. margin-top: 32px;
  300. font-size: 16px;
  301. margin-bottom: 1rem;
  302. text-transform: uppercase;
  303. font-weight: 600;
  304. letter-spacing: 0.1em;
  305. }
  306. .v-select {
  307. .v-field {
  308. border-radius: 0;
  309. }
  310. }
  311. .remaining-cars-notice {
  312. font-size: 13px;
  313. font-weight: 550;
  314. opacity: 0.6;
  315. }
  316. .submit-btn {
  317. border-radius: 0;
  318. font-weight: 600;
  319. }
  320. .error {
  321. color: red;
  322. width: 80%;
  323. margin: 0 auto 2em;
  324. text-align: center;
  325. }
  326. .legal {
  327. opacity: 0.7;
  328. font-size: 14px;
  329. font-style: italic;
  330. margin-left: auto;
  331. margin-right: auto;
  332. max-width: 80%;
  333. }
  334. }
  335. .confirmation-message {
  336. .v-card {
  337. .v-icon {
  338. color: green;
  339. }
  340. max-width: 1200px;
  341. padding: 24px;
  342. margin: 128px 0;
  343. font-weight: 500;
  344. }
  345. }
  346. </style>