Form.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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 de votre demande et pour vous informer sur nos offres.
  169. Elles sont destinées aux services Opentalent et à ses sous-traitants pour l’exécution des contrats.
  170. Conformément à la loi "Informatique et Libertés du 6 Janvier 1978", vous disposez d’un droit d’accès, de modifications, de rectification
  171. et de suppression des données vous concernant. Pour toute demande, adressez-vous à : OPENTALENT, 265 rue de la Grange 74950 SCIONZIER - FRANCE,
  172. opentalent.fr s’engage à la confidentialité et à la protection de vos données.”
  173. </div>
  174. </v-form>
  175. <div v-else class="confirmation-message d-flex flex-row justify-center">
  176. <v-card>
  177. <v-icon icon="fas fa-check mr-1" />
  178. Votre demande de contact a bien été enregistrée, nous reviendrons vers
  179. vous dès que possible.
  180. </v-card>
  181. </div>
  182. </LayoutContainer>
  183. </template>
  184. <script setup lang="ts">
  185. import { useRouter } from 'vue-router'
  186. import type { ComputedRef, Ref } from 'vue'
  187. import { reactive } from 'vue'
  188. import ContactRequest from '~/models/Maestro/ContactRequest'
  189. import { useEntityManager } from '~/composables/data/useEntityManager'
  190. const route = useRoute()
  191. const router = useRouter()
  192. const { em } = useEntityManager()
  193. const form: Ref<HTMLElement | null> = ref(null)
  194. const requestTypes: Array<{ id: string; label: string }> = [
  195. { id: 'CONTACT_REQUEST_INFORMATION', label: "Demande d'information" },
  196. { id: 'CONTACT_REQUEST_ESTIMATE', label: 'Demande de devis' },
  197. { id: 'CONTACT_REQUEST_DEMO', label: 'Demande de démonstration' },
  198. { id: 'CONTACT_REQUEST_OPTION', label: "Demande d'option supplémentaire" },
  199. { id: 'CONTACT_REQUEST_OTHER', label: 'Autre' },
  200. ]
  201. const products: Array<{ id: string; label: string }> = [
  202. { id: 'PRODUCT_AGENDA', label: 'Agenda culturel' },
  203. { id: 'PRODUCT_ARTIST', label: 'Opentalent Artist' },
  204. { id: 'PRODUCT_SCHOOL', label: 'Opentalent School' },
  205. { id: 'PRODUCT_MANAGER', label: 'Opentalent Manager' },
  206. { id: 'PRODUCT_ADVERTISING', label: 'Publicité' },
  207. { id: 'PRODUCT_OTHER', label: 'Autre' },
  208. ]
  209. const defaultRequestType = route.query.request ?? 'CONTACT_REQUEST_INFORMATION'
  210. // @ts-ignore
  211. const contactRequest: ContactRequest = reactive(
  212. em.newInstance(ContactRequest, { requestType: defaultRequestType })
  213. )
  214. // --- Validation ---
  215. const maxMessageLength = 2000
  216. const leftCars: ComputedRef<number> = computed(
  217. () =>
  218. maxMessageLength -
  219. (contactRequest.message ? contactRequest.message.length : 0)
  220. )
  221. const validateName = (name: string | null) => !!name || 'Le nom est obligatoire'
  222. const validateSurname = (surname: string | null) =>
  223. !!surname || 'Le prénom est obligatoire'
  224. const validatePostalCode = (postalCode: string | null) =>
  225. (!!postalCode && /^\d{5}$/.test(postalCode)) ||
  226. 'Le code postal doit être valide'
  227. const validateCity = (city: string | null) =>
  228. !!city || 'La ville est obligatoire'
  229. const validateEmail = (email: string | null) =>
  230. (!!email && /.+@.+\..+/.test(email)) || "L'adresse e-mail doit être valide"
  231. const validatePhone = (phone: string | null) =>
  232. (!!phone && /^((\+|00)33\s?|0)[1-9]([\s.]?\d{2}){4}$/.test(phone)) ||
  233. 'Le numéro de téléphone doit être valide'
  234. const validateStructureName = (structureName: string | null) =>
  235. !!structureName || 'Le nom de la structure est requis'
  236. const validateNonEmptyMessage = (message: string | null) =>
  237. (!!message && message.length > 0) || 'Le message ne peut pas être vide'
  238. const validateMessageLength = (message: string | null) =>
  239. (!!message && message.length <= maxMessageLength) ||
  240. 'Le message ne doit pas dépasser ' + maxMessageLength + ' caractères'
  241. const contactRequestSent: Ref<boolean> = ref(false)
  242. const errorMsg: Ref<string | null> = ref(null)
  243. /**
  244. * Submits the contact form.
  245. *
  246. * This function validates the form and sets the value of a variable to indicate whether the form submission was successful.
  247. *
  248. * @function
  249. *
  250. * @returns {void}
  251. */
  252. const submit = async (): Promise<void> => {
  253. const { valid } = await form.value!.validate()
  254. if (!valid) {
  255. contactRequestSent.value = false
  256. return
  257. }
  258. try {
  259. await em.persist(ContactRequest, contactRequest)
  260. } catch (e) {
  261. errorMsg.value =
  262. "Une erreur s'est produite, nous sommes navrés pour le désagrément. Veuillez réessayer plus tard."
  263. return
  264. }
  265. contactRequestSent.value = true
  266. errorMsg.value = null
  267. // Défile vers le début de page pour afficher le message de confirmation
  268. setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
  269. }
  270. </script>
  271. <style scoped lang="scss">
  272. h4 {
  273. display: flex;
  274. flex-direction: row;
  275. font-size: 40px;
  276. line-height: 95px;
  277. margin-bottom: 1rem;
  278. align-items: center;
  279. font-weight: 100;
  280. @media (max-width: 600px) {
  281. font-size: 24px;
  282. line-height: 48px;
  283. }
  284. .line {
  285. display: block;
  286. height: 1px;
  287. width: 64px;
  288. min-width: 64px;
  289. border-top: solid 1px var(--on-neutral-color);
  290. margin-right: 18px;
  291. }
  292. }
  293. .v-form {
  294. max-width: 1400px;
  295. margin: 0 auto;
  296. h6 {
  297. margin-top: 32px;
  298. font-size: 16px;
  299. margin-bottom: 1rem;
  300. text-transform: uppercase;
  301. font-weight: 600;
  302. letter-spacing: 0.1em;
  303. }
  304. .v-select {
  305. .v-field {
  306. border-radius: 0;
  307. }
  308. }
  309. .remaining-cars-notice {
  310. font-size: 13px;
  311. font-weight: 550;
  312. opacity: 0.6;
  313. }
  314. .submit-btn {
  315. border-radius: 0;
  316. font-weight: 600;
  317. }
  318. .error {
  319. color: red;
  320. width: 80%;
  321. margin: 0 auto 2em;
  322. text-align: center;
  323. }
  324. .legal {
  325. opacity: 0.7;
  326. font-size: 14px;
  327. font-style: italic;
  328. margin-left: auto;
  329. margin-right: auto;
  330. max-width: 80%;
  331. }
  332. }
  333. .confirmation-message {
  334. .v-card {
  335. .v-icon {
  336. color: green;
  337. }
  338. max-width: 1200px;
  339. padding: 24px;
  340. margin: 128px 0;
  341. font-weight: 500;
  342. }
  343. }
  344. </style>