Form.vue 11 KB

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