Skip to content

Ajouter un formulaire de contact à votre site Vitepress

Création du formulaire

Nous allons créer un composant Vue pour notre formulaire de contact.

Ce composant est à placer dans .vitepress/theme/components/ContactForm.vue:

vue
<script setup>
import { ref, computed } from 'vue'
import { useData } from 'vitepress'

const formData = ref({
  name: '',
  email: '',
  subject: '',
  message: ''
})

const isSubmitting = ref(false)
const isSuccess = ref(false)
const errorMessage = ref('')

const isValidEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return emailRegex.test(email)
}

const isFormValid = computed(() => {
  return formData.value.name.trim() !== '' &&
    formData.value.email.trim() !== '' &&
    isValidEmail(formData.value.email) &&
    formData.value.subject.trim() !== '' &&
    formData.value.message.trim() !== ''
})

const submitForm = async () => {
  errorMessage.value = ''
  if (!formData.value.name.trim()) {
    errorMessage.value = 'Name is required'
    return
  }
  if (!formData.value.email.trim()) {
    errorMessage.value = 'Email address is required'
    return
  }
  if (!isValidEmail(formData.value.email)) {
    errorMessage.value = 'Email address is invalid'
    return
  }
  if (!formData.value.subject.trim()) {
    errorMessage.value = 'Message subject is required'
    return
  }
  if (!formData.value.message.trim()) {
    errorMessage.value = 'Message is required'
    return
  }
  isSubmitting.value = true
  try {
    const formDataToSend = new FormData()
    formDataToSend.append('name', formData.value.name)
    formDataToSend.append('email', formData.value.email)
    formDataToSend.append('subject', formData.value.subject)
    formDataToSend.append('message', formData.value.message)
    const response = await fetch('/sendmail.php', {
      method: 'POST',
      body: formDataToSend
    })
    const message = await response.text()
    if (response.ok) {
      isSuccess.value = true
      errorMessage.value = ''
    } else {
      errorMessage.value = message || 'An error occurred. Please try again.'
    }
  } catch (error) {
    errorMessage.value = 'Network error'
  } finally {
    isSubmitting.value = false
  }
}
</script>

<template>
  <div class="contact-form">
    <form @submit.prevent="submitForm">
      <div class="form-group">
        <label for="name">Name <span class="required">*</span></label>
        <input
          id="name"
          v-model="formData.name"
          type="text"
          :disabled="isSuccess"
          placeholder="Your name"
        />
      </div>
      <div class="form-group">
        <label for="email">Email <span class="required">*</span></label>
        <input
          id="email"
          v-model="formData.email"
          type="email"
          :disabled="isSuccess"
          placeholder="Your email address"
        />
      </div>
      <div class="form-group">
        <label for="subject">Subject <span class="required">*</span></label>
        <input
          id="subject"
          v-model="formData.subject"
          type="text"
          :disabled="isSuccess"
          placeholder="Message subject"
        />
      </div>
      <div class="form-group">
        <label for="message">Message <span class="required">*</span></label>
        <textarea
          id="message"
          v-model="formData.message"
          :disabled="isSuccess"
          rows="5"
          placeholder="Your message"
        ></textarea>
      </div>
      <!-- Error message area -->
      <div v-if="errorMessage" class="error-message">
        {{ errorMessage }}
      </div>
      <!-- Success message area -->
      <div v-if="isSuccess" class="success-message">
        Thank you! Your message has been sent successfully.
      </div>
      <button
        v-if="!isSuccess"
        type="submit"
        :disabled="!isFormValid || isSubmitting || isSuccess"
        class="submit-button"
      >
        {{ isSubmitting ? 'Sending...' : 'Send message' }}
      </button>
    </form>
  </div>
</template>

<style scoped>
.contact-form {
  max-width: 600px;
  margin: 2rem auto;
}
.form-group {
  margin-bottom: 1.5rem;
}
label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: var(--vp-c-text-1);
}
.required {
  color: #e74c3c;
}
input,
textarea {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--vp-c-divider);
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
  background-color: var(--vp-c-bg);
  color: var(--vp-c-text-1);
  transition: border-color 0.2s;
}
input:focus,
textarea:focus {
  outline: none;
  border-color: var(--vp-c-brand);
}
input:disabled,
textarea:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  background-color: var(--vp-c-bg-soft);
}
textarea {
  resize: vertical;
}
.error-message {
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  background-color: #fee;
  border-left: 4px solid #e74c3c;
  color: #c0392b;
  border-radius: 4px;
}
.success-message {
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  background-color: #d4edda;
  border-left: 4px solid #28a745;
  color: #155724;
  border-radius: 4px;
  font-weight: 500;
}
.submit-button {
  width: 100%;
  padding: 0.75rem 1.5rem;
  background-color: var(--vp-c-brand-1);
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}
.submit-button:hover:not(:disabled) {
  background-color: var(--vp-c-brand-2);
}
.submit-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
</style>

Intégration du composant sur le site

Éditez .vitepress/theme/index.js pour ajouter:

javascript
  enhanceApp({ app }) {
    app.component('ContactForm', ContactForm)
  }

image Note: Si vous avez déjà enhanceApp dans ce fichier, par exemple sous la forme enhanceApp({ router }), complétez-le en y intégrant l'entrée ci-dessus et en ajoutant app comme suit: enhanceApp({ app, router }).

À présent, vous pouvez intégrer le composant dans n'importe quelle page Markdown comme ceci:

markdown
# Contact

Écrivez-moi avec le formulaire ci-dessous:

<ContactForm />

Côté serveur

Pour l'envoi effectif d'emails, nous allons utiliser le script PHP ci-dessous.

Dans l'en-tête, vous devez configurer vos coordonnées en modifiant CONTACT_INFO:

  • to_name et to_email: votre nom, et votre adresse email sur laquelle vous souhaitez recevoir les messages de vos visiteurs,
  • cc_name et cc_email: nom et addresse email de copie carbone des messages (facultatif),
  • site_email: l'adresse depuis laquelle votre site vous envoie les messages (vous pouvez mettre une adresse qui n'existe pas, comme no-reply@votre-domaine.tld).

image Note: Votre site vous enverra les messages des visiteurs en son nom, avec l'email site_email que vous aurez configuré. Cependant il configure un champ Reply-To dans les emails, qui vous permettra de répondre aux messages en toute transparence.

Ce script est à placer dans public/sendmail.php:

php
<?php

$CONTACT_INFO['to_email'] = 'me@example.com';
$CONTACT_INFO['to_name'] = 'My name';
$CONTACT_INFO['cc_email'] = '';
$CONTACT_INFO['cc_name'] = '';
$CONTACT_INFO['site_email'] = 'My website <no-reply@example.com>';

header("Content-Type: text/plain");

$from_name = trim($_POST['name']);
$from_email = trim($_POST['email']);
$subject = trim($_POST['subject']);
$message = trim($_POST['message']);

$regex_head = '/[\n\r]/';
$regex_mail = '/^[-+.\w]{1,64}@[-.\w]{1,64}\.[-.\w]{2,6}$/i';

if (empty($from_name)
  || empty($from_email)
  || empty($subject)
  || empty($message)) {
  http_response_code(400);
  echo 'Required fields must be filled';
} else if (!preg_match($regex_mail, $from_email)) {
  http_response_code(400);
  echo 'Invalid email address';
} else if (preg_match($regex_head, $from_name)
  || preg_match($regex_head, $from_email)
  || preg_match($regex_head, $subject)) {
  http_response_code(400);
  echo 'Malformed data';
} else {
  $to = $CONTACT_INFO['to_name']
    .'<'.$CONTACT_INFO['to_email'].'>';
  $headers = 'From: '.$CONTACT_INFO['site_email']."\r\n";
  $headers .= 'Reply-To: '.$from_name
    .' <'.$from_email.'>'."\r\n";
  if (!empty($CONTACT_INFO['cc_email'])) {
    $headers .= 'Cc: '.$CONTACT_INFO['cc_name']
      .' <'.$CONTACT_INFO['cc_email'].'>'."\r\n";
  }
  $headers .= "MIME-Version: 1.0\r\n";
  $headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
  $headers .= "Content-Transfer-Encoding: base64\r\n";

  $contents = vsprintf('
%s

--------
%s <%s>', array(
    $message, $from_name, $from_email
  ));
  $contents = chunk_split(base64_encode($contents));
  if(!mail($to, $subject, $contents, $headers)) {
    http_response_code(500);
    echo 'Unable to send message';
  }
}

?>

Vous pouvez si besoin modifier le composant Vue ainsi que le script PHP pour leur adjoindre une protection anti-spam.

Licence

Tout le code présenté sur cette page est sous licence CC0 1.0 Universal.

Vous pouvez le copier, le modifier, le distribuer et l'utiliser, même à des fins commerciales, sans demander la permission.