Form.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <div>
  3. <v-card v-if="!jobApplicationSent">
  4. <v-card-title class="text-center">
  5. Formulaire de Candidature
  6. </v-card-title>
  7. <v-card-text>
  8. <v-form ref="form" validate-on="submit lazy" @submit.prevent="submit">
  9. <v-text-field
  10. id="jobApplicationName"
  11. v-model="jobApplication.name"
  12. :rules="[validateName]"
  13. label="Nom*"
  14. required
  15. />
  16. <v-text-field
  17. id="jobApplicationSurname"
  18. v-model="jobApplication.surname"
  19. :rules="[validateSurname]"
  20. label="Prénom*"
  21. required
  22. />
  23. <v-text-field
  24. id="jobApplicationPhone"
  25. v-model="jobApplication.phone"
  26. :rules="[validatePhone]"
  27. label="Téléphone*"
  28. required
  29. />
  30. <v-text-field
  31. id="jobApplicationEmail"
  32. v-model="jobApplication.email"
  33. :rules="[validateEmail]"
  34. label="Email*"
  35. required
  36. />
  37. <v-file-input
  38. id="jobApplicationResume"
  39. v-model="resumeUpload"
  40. :rules="[validateResume, validateFileSize]"
  41. label="Dépôt de CV*"
  42. accept=".pdf, .jpeg, .png"
  43. show-size
  44. required
  45. />
  46. <v-file-input
  47. id="jobApplicationMotivationLetter"
  48. v-model="motivationLetterUpload"
  49. :rules="[validateFileSize]"
  50. label="Dépôt de lettre de motivation"
  51. accept=".pdf, .jpeg, .png"
  52. show-size
  53. />
  54. <v-textarea
  55. id="jobApplicationMessage"
  56. v-model="jobApplication.message"
  57. :rules="[validateNonEmptyMessage, validateMessageLength]"
  58. label="Message*"
  59. required
  60. />
  61. <span class="remaining-cars-notice"
  62. >{{ leftCars }} caractères restants</span
  63. >
  64. <div class="d-flex flex-column align-center mt-4">
  65. <!-- @see https://github.com/hCaptcha/vue-hcaptcha -->
  66. <LayoutCaptcha />
  67. </div>
  68. </v-form>
  69. </v-card-text>
  70. <p class="text-right mr-6">* Champs obligatoires</p>
  71. <v-card-actions class="justify-center">
  72. <v-btn class="btn-more mb-4 submit" @click="submit"> Envoyer </v-btn>
  73. </v-card-actions>
  74. </v-card>
  75. </div>
  76. </template>
  77. <script setup lang="ts">
  78. import type { ComputedRef, Ref } from 'vue'
  79. import { reactive } from 'vue'
  80. import ContactRequest from '~/models/Maestro/ContactRequest'
  81. import { useEntityManager } from '~/composables/data/useEntityManager'
  82. import JobApplication from '~/models/Maestro/JobApplication'
  83. import FileUtils from '~/services/utils/FileUtils'
  84. const { em } = useEntityManager()
  85. const form: Ref<HTMLElement | null> = ref(null)
  86. const jobApplicationSent: Ref<boolean> = ref(false)
  87. const emit = defineEmits(['submit'])
  88. // @ts-ignore
  89. const jobApplication: ContactRequest = reactive(em.newInstance(JobApplication))
  90. const resumeUpload = ref(null)
  91. const motivationLetterUpload = ref(null)
  92. // --- Validation ---
  93. const maxMessageLength = 2000
  94. const leftCars: ComputedRef<number> = computed(
  95. () =>
  96. maxMessageLength -
  97. (jobApplication.message ? jobApplication.message.length : 0)
  98. )
  99. // Taille maximum en Mo
  100. const maxFileSize = 5
  101. const validateName = (name: string | null) => !!name || 'Le nom est obligatoire'
  102. const validateSurname = (surname: string | null) =>
  103. !!surname || 'Le prénom est obligatoire'
  104. const validateEmail = (email: string | null) =>
  105. (!!email && /.+@.+\..+/.test(email)) || "L'adresse e-mail doit être valide"
  106. const validatePhone = (email: string | null) =>
  107. (!!email && /^((\+|00)33\s?|0)[1-7]([\s.]?\d{2}){4}$/.test(email)) ||
  108. 'Le numéro de téléphone doit être valide'
  109. const validateResume = () =>
  110. (resumeUpload.value !== null && resumeUpload.value[0] !== null) ||
  111. "Vous devez joindre un CV à l'un des formats indiqués"
  112. const validateFileSize = () =>
  113. (resumeUpload.value !== null &&
  114. // @ts-ignore
  115. resumeUpload.value.size < maxFileSize * 1024 * 1024) ||
  116. 'La taille du fichier ne doit pas dépasser ' + maxFileSize + ' Mo'
  117. const validateNonEmptyMessage = (message: string | null) =>
  118. (!!message && message.length > 0) || 'Le message ne peut pas être vide'
  119. const validateMessageLength = (message: string | null) =>
  120. (!!message && message.length <= maxMessageLength) ||
  121. 'Le message ne doit pas dépasser ' + maxMessageLength + ' caractères'
  122. /**
  123. * Soumet le formulaire de candidature (boite de dialogue)
  124. */
  125. const submit = async () => {
  126. jobApplication.resume =
  127. resumeUpload.value !== null
  128. ? {
  129. // @ts-ignore
  130. name: resumeUpload.value.name,
  131. content: await FileUtils.blobToBase64(resumeUpload.value),
  132. }
  133. : null
  134. jobApplication.motivationLetter =
  135. motivationLetterUpload.value !== null
  136. ? {
  137. // @ts-ignore
  138. name: motivationLetterUpload.value.name,
  139. content: await FileUtils.blobToBase64(
  140. motivationLetterUpload.value
  141. ),
  142. }
  143. : null
  144. const { valid } = await form.value!.validate()
  145. if (!valid) {
  146. jobApplicationSent.value = false
  147. return
  148. }
  149. await em.persist(JobApplication, jobApplication)
  150. jobApplicationSent.value = true
  151. emit('submit')
  152. }
  153. </script>
  154. <style scoped lang="scss">
  155. .submit {
  156. width: 100%;
  157. margin-bottom: 0 !important;
  158. height: 55px;
  159. background: var(--secondary-color);
  160. }
  161. .submit:hover {
  162. background-color: var(--on-neutral-color-extra-light);
  163. }
  164. </style>