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, reactive, Ref } from "vue";
  189. import ContactRequest from "~/models/Maestro/ContactRequest";
  190. import { useEntityManager } from "~/composables/data/useEntityManager";
  191. const route = useRoute();
  192. const router = useRouter();
  193. const { em } = useEntityManager();
  194. const form: Ref<HTMLElement | null> = ref(null);
  195. const requestTypes: Array<{ id: string; label: string }> = [
  196. { id: "CONTACT_REQUEST_INFORMATION", label: "Demande d'information" },
  197. { id: "CONTACT_REQUEST_ESTIMATE", label: "Demande de devis" },
  198. { id: "CONTACT_REQUEST_DEMO", label: "Demande de démonstration" },
  199. { id: "CONTACT_REQUEST_OPTION", label: "Demande d'option supplémentaire" },
  200. { id: "CONTACT_REQUEST_OTHER", label: "Autre" },
  201. ];
  202. const products: Array<{ id: string; label: string }> = [
  203. { id: "PRODUCT_AGENDA", label: "Agenda culturel" },
  204. { id: "PRODUCT_ARTIST", label: "Opentalent Artist" },
  205. { id: "PRODUCT_SCHOOL", label: "Opentalent School" },
  206. { id: "PRODUCT_MANAGER", label: "Opentalent Manager" },
  207. { id: "PRODUCT_ADVERTISING", label: "Publicité" },
  208. { id: "PRODUCT_OTHER", label: "Autre" },
  209. ];
  210. const defaultRequestType = route.query.request ?? "CONTACT_REQUEST_INFORMATION";
  211. // @ts-ignore
  212. const contactRequest: ContactRequest = reactive(
  213. em.newInstance(ContactRequest, { requestType: defaultRequestType }),
  214. );
  215. // --- Validation ---
  216. const maxMessageLength = 2000;
  217. const leftCars: ComputedRef<number> = computed(
  218. () =>
  219. maxMessageLength -
  220. (contactRequest.message ? contactRequest.message.length : 0),
  221. );
  222. const validateName = (name: string | null) =>
  223. !!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>