feat: mise à jour du formulaire de contact, ajout de la gestion des sujets et amélioration de la présentation des images

This commit is contained in:
Boris Waaub
2026-02-26 17:38:23 +01:00
parent c350ea3dcd
commit a3383a97df
10 changed files with 238 additions and 97 deletions

View File

@@ -13,17 +13,20 @@ contactForm:
qrLabel: "ou scannez le code QR :"
qrImg: "/images/chill-admin.png"
fields:
- name: "sujet"
- name: "subject"
label: "Sujet"
type: "select"
required: true
options:
- value: "support"
label: "Support technique"
- value: "demo"
label: "Demander une démo"
- value: "quote"
label: "Demander un devis"
- value: "formation"
- value: "training"
label: "Formation"
- value: "support"
label: "Support technique"
- value: "other"
label: "Autre"
- name: "email"

View File

@@ -7,7 +7,7 @@ badge: "Droits daccès"
badgeColor: "rapport-rgpd"
demo:
description: "Assurez la conformité RGPD et organisez vos rapports et documents."
image:
images:
- "/images/features/HP-droitsdacces.png"
---

View File

@@ -7,9 +7,10 @@ badge: "Statistiques"
badgeColor: "rapports-statistiques"
demo:
description: "Générez et exportez des rapports statistiques détaillés."
image:
images:
- "/images/features/HP-exports.png"
---
## Présentation

View File

@@ -50,7 +50,7 @@ layout: "simple"
"additional_description": "* A partir du 4ème utilisateur: 40€ supplémentaire/an.",
"button": {
"text": "Demander un devis",
"url": "/contact/"
"url": "/contact?subject=quote"
}
},
{
@@ -68,7 +68,7 @@ layout: "simple"
"additional_description": "* A partir du 4ème utilisateur: 40€ supplémentaire/an.",
"button": {
"text": "Demander un devis",
"url": "/contact/"
"url": "/contact?subject=quote"
}
},
{
@@ -82,8 +82,8 @@ layout: "simple"
"Mises à jour régulières"
],
"button": {
"text": "Contactez-nous",
"url": "/contact/"
"text": "Demander un devis",
"url": "/contact?subject=quote"
}
},
{
@@ -98,7 +98,7 @@ layout: "simple"
],
"button": {
"text": "Contactez-nous",
"url": "/contact/"
"url": "/contact?subject=training"
}
}

View File

@@ -21,6 +21,6 @@ layout: "gradient-card"
{{< /gradient-card-section >}}
{{< gradient-card-section title="Formation des utilisateurs" description="Organisez une journée de formation à l'utilisation de Chill." >}}
<a href="/contact" class="btn btn-primary mt-4">Contactez-nous</a>
<a href="/contact?subject=training" class="btn btn-primary mt-4">Contactez-nous</a>
{{< /gradient-card-section >}}

View File

@@ -100,7 +100,7 @@ pagination = { pagerSize = 6, path = "page" }
# Primary button
[params.cta.primary_button]
text = "Réserver une démo"
url = "/contact"
url = "/contact?subject=demo"
open_tab = false
# Secondary button

View File

@@ -765,34 +765,58 @@ body {
width: 100%;
}
.\!container {
width: 100% !important;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
.\!container {
max-width: 640px !important;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
.\!container {
max-width: 768px !important;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
.\!container {
max-width: 1024px !important;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
.\!container {
max-width: 1280px !important;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
.\!container {
max-width: 1536px !important;
}
}
.prose {
@@ -1895,6 +1919,28 @@ body {
}
}
.\!container {
margin-left: auto;
margin-right: auto;
max-width: 80rem;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 640px) {
.\!container {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
@media (min-width: 1024px) {
.\!container {
padding-left: 2rem;
padding-right: 2rem;
}
}
.section {
padding-top: 2rem;
padding-bottom: 2rem;
@@ -2349,10 +2395,6 @@ body {
margin-top: 1rem;
}
.mt-5 {
margin-top: 1.25rem;
}
.mt-6 {
margin-top: 1.5rem;
}
@@ -2428,6 +2470,10 @@ body {
height: 10rem;
}
.h-48 {
height: 12rem;
}
.h-5 {
height: 1.25rem;
}
@@ -2456,10 +2502,6 @@ body {
height: 100%;
}
.h-48 {
height: 12rem;
}
.max-h-12 {
max-height: 3rem;
}
@@ -2496,6 +2538,10 @@ body {
width: 10rem;
}
.w-48 {
width: 12rem;
}
.w-5 {
width: 1.25rem;
}
@@ -2520,10 +2566,6 @@ body {
width: 100%;
}
.w-48 {
width: 12rem;
}
.\!max-w-none {
max-width: none !important;
}
@@ -2950,10 +2992,6 @@ body {
background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1));
}
.bg-gradient-to-b {
background-image: linear-gradient(to bottom, var(--tw-gradient-stops));
}
.bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
@@ -2964,25 +3002,10 @@ body {
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-secondary-50 {
--tw-gradient-from: #ddeef6 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(221 238 246 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-secondary-50 {
--tw-gradient-to: rgb(221 238 246 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #ddeef6 var(--tw-gradient-via-position), var(--tw-gradient-to);
}
.to-primary-700 {
--tw-gradient-to: #8c2908 var(--tw-gradient-to-position);
}
.to-white {
--tw-gradient-to: #fff var(--tw-gradient-to-position);
}
.object-contain {
-o-object-fit: contain;
object-fit: contain;
@@ -3106,10 +3129,6 @@ body {
padding-bottom: 6rem;
}
.pb-32 {
padding-bottom: 8rem;
}
.pl-4 {
padding-left: 1rem;
}
@@ -3158,10 +3177,6 @@ body {
text-align: right;
}
.text-start {
text-align: start;
}
.font-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
@@ -3376,12 +3391,6 @@ body {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-2xl {
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);

View File

@@ -77,15 +77,7 @@
<p class="mt-6 text-lg leading-8 text-gray-600">{{ .Params.demo.description }}</p>
</div>
<div class="mt-16 flex justify-center">
<div class="relative rounded-xl bg-white p-8 shadow-2xl ring-1 ring-gray-200">
<div class="carousel">
{{ range .Params.demo.images }}
<div class="carousel-item">
<img src="{{ . }}" alt="Demo" class="rounded-lg">
</div>
{{ end }}
</div>
</div>
{{ partial "shortcodes/carousel.html" (dict "images" .Params.demo.images) }}
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,122 @@
{{/*
Hugo List to Carousel Partial
Source: https://hugocodex.org/add-ons/slider-carousel/
Author: Jeroen van der Schee
*/}}
{{ $id := printf "carousel-%d" (now.UnixNano) }}
<div id="{{ $id }}" class="carousel-container">
<div class="carousel-slides">
{{ range .images }}
<div class="carousel-slide">
<img src="{{ . }}" alt="Carousel image" loading="lazy" />
</div>
{{ end }}
</div>
{{ if gt (len .images) 1 }}
<button class="carousel-prev" aria-label="Précédent">&#10094;</button>
<button class="carousel-next" aria-label="Suivant">&#10095;</button>
<div class="carousel-dots">
{{ range $i, $_ := .images }}
<span class="carousel-dot" data-index="{{ $i }}"></span>
{{ end }}
</div>
{{ end }}
</div>
<style>
.carousel-container {
position: relative;
width: 100%;
margin: 0 auto;
}
.carousel-slides {
display: flex;
overflow: hidden;
}
.carousel-slide {
min-width: 100%;
transition: transform 0.5s ease;
display: flex;
justify-content: center;
}
.carousel-slide img {
max-width: 100%;
border-radius: 1rem;
}
.carousel-prev,
.carousel-next {
position: absolute;
top: 50%;
transform: translateY(-50%);
border: none;
font-size: 2rem;
padding: 0.5rem 1rem;
cursor: pointer;
border-radius: 50%;
z-index: 2;
}
.carousel-prev {
left: 10px;
}
.carousel-next {
right: 10px;
}
.carousel-dots {
text-align: center;
margin-top: 1rem;
}
.carousel-dot {
display: inline-block;
width: 12px;
height: 12px;
margin: 0 4px;
background: #bbb;
border-radius: 50%;
cursor: pointer;
}
.carousel-dot.active {
background: #333;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
var container = document.getElementById('{{ $id }}');
if (!container) return;
var slides = container.querySelectorAll('.carousel-slide');
var prevBtn = container.querySelector('.carousel-prev');
var nextBtn = container.querySelector('.carousel-next');
var dots = container.querySelectorAll('.carousel-dot');
var current = 0;
function showSlide(idx) {
slides.forEach(function (slide, i) {
slide.style.transform = 'translateX(' + (-100 * idx) + '%)';
});
dots.forEach(function (dot, i) {
dot.classList.toggle('active', i === idx);
});
current = idx;
}
prevBtn.addEventListener('click', function () {
showSlide((current - 1 + slides.length) % slides.length);
});
nextBtn.addEventListener('click', function () {
showSlide((current + 1) % slides.length);
});
dots.forEach(function (dot, i) {
dot.addEventListener('click', function () { showSlide(i); });
});
showSlide(0);
});
</script>

View File

@@ -2,42 +2,56 @@
<form id="contactForm" class="max-w-lg mx-auto p-6 bg-white rounded shadow" method="POST" action="{{ $form.action | default "http://localhost:3001/contact" }}">
<form id="contactForm" class="max-w-lg mx-auto p-6 bg-white rounded shadow" method="POST"
action="{{ $form.action | default " http://localhost:3001/contact" }}">
<h3 class="text-2xl font-bold mb-4">{{ $form.title }}</h3>
{{ range $form.fields }}
<div class="mb-4">
{{ if eq .type "textarea" }}
<label for="{{ .name }}">{{ .label }}</label>
<textarea id="{{ .name }}" name="{{ .name }}" rows="5" {{ if .required }}required{{ end }} class="w-full px-3 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300"></textarea>
{{ else if eq .type "select" }}
<select id="{{ .name }}" name="{{ .name }}" {{ if .required }}required{{ end }} class="w-full px-3 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300">
{{ range .options }}
<option value="{{ .value }}">{{ .label | default .value }}</option>
{{ end }}
</select>
{{ else }}
<input type="{{ .type }}" placeholder="{{.label}}" id="{{ .name }}" name="{{ .name }}" {{ if .required }}required{{ end }} class="w-full px-3 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300" />
<div class="mb-4">
{{ if eq .type "textarea" }}
<label for="{{ .name }}">{{ .label }}</label>
<textarea id="{{ .name }}" name="{{ .name }}" rows="5" {{ if .required }}required{{ end }}
class="w-full px-3 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300"></textarea>
{{ else if eq .type "select" }}
<select id="{{ .name }}" name="{{ .name }}" {{ if .required }}required{{ end }}
class="w-full px-3 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300">
{{ range .options }}
<option value="{{ .value }}">{{ .label | default .value }}</option>
{{ end }}
</div>
</select>
{{ else }}
<input type="{{ .type }}" placeholder="{{.label}}" id="{{ .name }}" name="{{ .name }}" {{ if .required }}required{{
end }} class="w-full px-3 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300" />
{{ end }}
</div>
{{ end }}
<!-- Champ honeypot invisible pour les robots -->
<div style="position:absolute; left:-9999px; top:auto; width:1px; height:1px; overflow:hidden;">
<label for="website">Ne pas remplir ce champ</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" />
</div>
<div class=" text-md text-gray-500 mt-1 prose prose-sm" >
<div class=" text-md text-gray-500 mt-1 prose prose-sm">
{{ $form.hint | markdownify | safeHTML }}
</div>
<div class="flex justify-end mt-4">
<button type="submit" class="px-6 py-3 rounded-lg font-bold transition duration-200 ease-in-out border-2 text-primary-400 border-primary-400 hover:border-primary-400 hover:text-primary-400 hover:scale-105">
<button type="submit"
class="px-6 py-3 rounded-lg font-bold transition duration-200 ease-in-out border-2 text-primary-400 border-primary-400 hover:border-primary-400 hover:text-primary-400 hover:scale-105">
{{ $form.button.label | default "Envoyer" }}
</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('contactForm');
const resultDiv = document.getElementById('contact-result');
form.addEventListener('submit', function(e) {
// Pré-sélectionner le champ "sujet" si paramètre dans l'URL
const urlParams = new URLSearchParams(window.location.search);
const subject = urlParams.get('subject');
if (subject) {
const select = document.getElementById('subject');
if (select) {
select.value = subject;
}
}
form.addEventListener('submit', function (e) {
e.preventDefault();
resultDiv.innerHTML = '';
const formData = new FormData(form);
@@ -45,18 +59,18 @@
method: 'POST',
body: formData
})
.then(async response => {
const text = await response.text();
if (response.status === 200) {
resultDiv.innerHTML = `<div style='background:#e6ffed;color:#228B22;padding:1em;border-radius:6px;border:1px solid #228B22;'>${text}</div>`;
form.reset();
} else {
resultDiv.innerHTML = `<div style='background:#ffe6e6;color:#b22222;padding:1em;border-radius:6px;border:1px solid #b22222;'>${text}</div>`;
}
})
.catch(err => {
resultDiv.innerHTML = `<div style='background:#ffe6e6;color:#b22222;padding:1em;border-radius:6px;border:1px solid #b22222;'>Erreur réseau</div>`;
});
.then(async response => {
const text = await response.text();
if (response.status === 200) {
resultDiv.innerHTML = `<div style='background:#e6ffed;color:#228B22;padding:1em;border-radius:6px;border:1px solid #228B22;'>${text}</div>`;
form.reset();
} else {
resultDiv.innerHTML = `<div style='background:#ffe6e6;color:#b22222;padding:1em;border-radius:6px;border:1px solid #b22222;'>${text}</div>`;
}
})
.catch(err => {
resultDiv.innerHTML = `<div style='background:#ffe6e6;color:#b22222;padding:1em;border-radius:6px;border:1px solid #b22222;'>Erreur réseau</div>`;
});
});
});
</script>