Add contact page

This commit is contained in:
2026-01-17 05:43:53 +01:00
parent 95623cb0cc
commit 1eb76ec064
20 changed files with 1054 additions and 17 deletions

View File

@@ -1,9 +1,7 @@
@import "tailwindcss";
@plugin "@tailwindcss/forms";
@source not "../../public";
@theme {
--font-sans: InterVariable, sans-serif;
--font-sans--font-feature-settings: "cv02", "cv03", "cv04", "cv11";
--font-sans: InterVariable, sans-serif;
--font-sans--font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
@import "./css/swiper.css";

View File

@@ -1,8 +1,6 @@
twig:
file_name_pattern: "*.twig"
form_themes:
- "form/custom_tailwind_theme.html.twig"
file_name_pattern: "*.twig"
when@test:
twig:
strict_variables: true
twig:
strict_variables: true

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260117031003 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE configuration (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, owner_mail VARCHAR(255) DEFAULT NULL, PRIMARY KEY (id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE configuration');
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260117033945 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE contact (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, send_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, is_valid BOOLEAN NOT NULL, PRIMARY KEY (id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE contact');
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Controller;
use App\Entity\Configuration;
use App\Form\ConfigurationType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ConfigurationController extends AbstractController
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/admin/configuration', name: 'admin_configuration_index')]
public function index(Request $request): Response
{
$configuration = $this->entityManager->getRepository(Configuration::class)->findOneBy(['id' => 1]);
$form = $this->createForm(ConfigurationType::class, $configuration);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$isConfiguration = $this->entityManager->getRepository(Configuration::class)->findOneBy(['id' => 1]);
if ($isConfiguration) {
$isConfiguration->setOwnerMail($form->getData()->getOwnerMail());
} else {
$configuration = new Configuration();
$configuration->setOwnerMail($form->getData()->getOwnerMail());
$this->entityManager->persist($configuration);
}
$this->entityManager->flush();
}
return $this->render('configuration/index.html.twig', [
'form' => $form->createView(),
]);
}
}

View File

@@ -3,18 +3,26 @@
namespace App\Controller;
use App\Entity\News;
use App\Entity\Contact;
use App\Form\ContactType;
use App\Service\MailService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class PageController extends AbstractController
{
private EntityManagerInterface $entityManager;
private MailService $mailService;
public function __construct(EntityManagerInterface $entityManager)
public function __construct(EntityManagerInterface $entityManager, MailService $mailService)
{
$this->entityManager = $entityManager;
$this->mailService = $mailService;
}
#[Route('/', name: 'home')]
@@ -47,6 +55,43 @@ final class PageController extends AbstractController
return $this->render('page/show.html.twig', []);
}
#[Route('/contact', name: 'contact')]
public function contact(Request $request): Response
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$contact = new Contact();
$contact->setSendAt(new DateTimeImmutable());
$data = $form->getData();
$honeyPot = $form->getData()['mobilePhoneNumber'];
if ($honeyPot !== NULL) {
$contact->setIsValid(false);
$this->addFlash('success', 'Votre message a été envoyé avec succès.');
$this->entityManager->persist($contact);
$this->entityManager->flush();
return $this->redirectToRoute('contact');
}
try {
$this->mailService->sendContactMail($data);
$contact->setIsValid(true);
$this->addFlash('success', 'Votre message a été envoyé avec succès.');
} catch (Exception $e) {
$contact->setIsValid(false);
$this->addFlash('error', 'Une erreur est survenue lors de lenvoi du message.');
}
$this->entityManager->persist($contact);
$this->entityManager->flush();
return $this->redirectToRoute('contact');
}
return $this->render('page/contact.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/mentions-legales', name: 'legalmentions')]
public function legalmentions(): Response
{

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Entity;
use App\Repository\ConfigurationRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ConfigurationRepository::class)]
class Configuration
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $ownerMail = null;
public function getId(): ?int
{
return $this->id;
}
public function getOwnerMail(): ?string
{
return $this->ownerMail;
}
public function setOwnerMail(?string $ownerMail): static
{
$this->ownerMail = $ownerMail;
return $this;
}
}

50
src/Entity/Contact.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace App\Entity;
use App\Repository\ContactRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ContactRepository::class)]
class Contact
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?\DateTimeImmutable $sendAt = null;
#[ORM\Column]
private ?bool $isValid = null;
public function getId(): ?int
{
return $this->id;
}
public function getSendAt(): ?\DateTimeImmutable
{
return $this->sendAt;
}
public function setSendAt(\DateTimeImmutable $sendAt): static
{
$this->sendAt = $sendAt;
return $this;
}
public function isValid(): ?bool
{
return $this->isValid;
}
public function setIsValid(bool $isValid): static
{
$this->isValid = $isValid;
return $this;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Form;
use App\Entity\Configuration;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ConfigurationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('ownerMail', EmailType::class, [
'label' => 'Email du propriétaire',
'label_attr' => [
'class' => 'block text-sm font-semibold text-gray-900',
],
'attr' => [
'class' => 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 border border-gray-300 placeholder:text-gray-400 focus:border-amber-600 focus:ring-2 focus:ring-amber-600/30 focus:outline-none',
],
'row_attr' => [
'class' => 'space-y-1',
],
])
->add('submit', SubmitType::class, [
'label' => 'Enregistrer',
'attr' => [
'class' => 'mt-2 inline-flex items-center justify-center rounded-md bg-amber-600 px-3.5 py-2.5 text-base font-semibold text-white shadow-sm hover:bg-amber-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600',
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Configuration::class,
]);
}
}

47
src/Form/ContactType.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('lastName', TextType::class, [
'constraints' => [new Assert\NotBlank()],
])
->add('firstName', TextType::class, [
'constraints' => [new Assert\NotBlank()],
])
->add('company', TextType::class, [
'required' => false,
])
->add('email', EmailType::class, [
'constraints' => [
new Assert\NotBlank(),
new Assert\Email(),
],
])
->add('phoneNumber', TextType::class, [
'required' => false,
])
->add('mobilePhoneNumber', TextType::class, [
'required' => false,
])
->add('message', TextareaType::class, [
'constraints' => [new Assert\NotBlank()],
])
->add('agreeToPolicies', CheckboxType::class, [
'constraints' => [new Assert\IsTrue()],
])
;
}
}

View File

@@ -19,6 +19,15 @@ class NewsType extends AbstractType
$builder
->add('title', TextType::class, [
'label' => 'Titre',
'label_attr' => [
'class' => 'block text-sm font-semibold text-gray-900',
],
'attr' => [
'class' => 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 border border-gray-300 placeholder:text-gray-400 focus:border-amber-600 focus:ring-2 focus:ring-amber-600/30 focus:outline-none',
],
'row_attr' => [
'class' => 'space-y-1',
],
])
->add('description', CKEditor5Type::class, [
'required' => false,

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Configuration;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Configuration>
*/
class ConfigurationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Configuration::class);
}
// /**
// * @return Configuration[] Returns an array of Configuration objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('c.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Configuration
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Contact;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Contact>
*/
class ContactRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Contact::class);
}
// /**
// * @return Contact[] Returns an array of Contact objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('c.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Contact
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Service;
use App\Entity\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class MailService
{
private MailerInterface $mailer;
private EntityManagerInterface $entityManager;
public function __construct(MailerInterface $mailer, EntityManagerInterface $entityManager)
{
$this->mailer = $mailer;
$this->entityManager = $entityManager;
}
public function sendContactMail(array $data): void
{
$configuration = $this->entityManager->getRepository(Configuration::class)->findOneBy(['id' => 1]);
$email = (new TemplatedEmail())
->from($configuration->getOwnerMail())
->to($configuration->getOwnerMail())
->replyTo($data["email"])
->subject('Nouvelle demande de contact')
->htmlTemplate('mail/contact.html.twig')
->context([
'data' => $data,
]);
$this->mailer->send($email);
}
}

View File

@@ -315,6 +315,19 @@
<span class="truncate">Fonctionnalité</span>
</a>
</li>
<li>
<a
href="{{ path('admin_configuration_index') }}"
class="group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold hover:bg-gray-50 hover:text-amber-600{% if path starts with '/admin/configuration' %} bg-gray-50 text-amber-600 {% else %} text-gray-700 hover:bg-gray-50 hover:text-amber-600{% endif %}"
>
<span
class="flex size-6 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-white text-[0.625rem] font-medium {% if path starts with '/admin/configuration' %} border-amber-600 text-amber-600 {% else %} text-gray-400 group-hover:border-amber-600 group-hover:text-amber-600{% endif %}"
>
C
</span>
<span class="truncate">Configuration</span>
</a>
</li>
</ul>
</li>
{% endif %}

View File

@@ -64,8 +64,8 @@
Me connaître
</a>
<a
href="tel:+33672701956"
class="text-sm/6 font-semibold text-gray-900 bg-amber-300 rounded-2xl px-4 py-2 hover:bg-amber-400 transition-colors"
href="{{ path('contact') }}"
class="text-sm/6 font-semibold text-gray-900"
>
Me contacter
</a>

View File

@@ -0,0 +1,27 @@
{% extends 'base_admin.html.twig' %}
{% block title %}
Configuration
{% endblock %}
{% block body %}
<main class="lg:pl-72">
<div class="xl:pr-96">
<div class="px-4 py-10 sm:px-6 lg:px-8 lg:py-6">
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
</div>
</div>
</main>
<aside
class="fixed inset-y-0 right-0 hidden w-96 overflow-y-auto border-l border-gray-200 px-4 py-6 sm:px-6 lg:px-8 xl:block dark:border-white/10"
>
<a
href="#"
class="rounded-md bg-amber-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-amber-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600 dark:bg-amber-500 dark:shadow-none dark:hover:bg-amber-400 dark:focus-visible:outline-amber-500"
>
Modifier la configuration
</a>
</aside>
{% endblock %}

View File

@@ -1,5 +0,0 @@
{% use 'tailwind_2_layout.html.twig' %}
{%- block form_row -%}
{%- set row_class = row_class|default('mb-4 flex flex-col space-y-1') -%}
{{- parent() -}}{%- endblock form_row -%}{%- block widget_attributes -%}{%- set widget_class = widget_class|default('block w-full rounded-lg border-gray-300 focus:border-amber-500 focus:ring-amber-500') -%}{%- set widget_disabled_class = widget_disabled_class|default('opacity-50 cursor-not-allowed') -%}{%- set widget_errors_class = widget_errors_class|default('border-red-500 ring-red-500') -%}{{- parent() -}}{%- endblock widget_attributes -%}{%- block form_label -%}{%- set label_class = label_class|default('font-medium text-gray-700') -%}{{- parent() -}}{%- endblock form_label -%}{%- block form_help -%}{%- set help_class = help_class|default('text-sm text-gray-500') -%}{{- parent() -}}{%- endblock form_help -%}{%- block form_errors -%}{%- set error_item_class = error_item_class|default('text-sm text-red-600') -%}{{- parent() -}}{%- endblock form_errors -%}

View File

@@ -0,0 +1,238 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>
Nouveau message de contact
</title>
<style media="all" type="text/css">
/* ------------------------------------- GLOBAL RESETS
------------------------------------- */
body { font-family: Helvetica, sans-serif; -webkit-font-smoothing:
antialiased; font-size: 16px; line-height: 1.3; -ms-text-size-adjust:
100%; -webkit-text-size-adjust: 100%; }
table { border-collapse: separate; mso-table-lspace: 0pt;
mso-table-rspace: 0pt; width: 100%; }
table td { font-family: Helvetica, sans-serif; font-size: 16px;
vertical-align: top; } /* ------------------------------------- BODY &
CONTAINER ------------------------------------- */
body { background-color: #f4f5f6; margin: 0; padding: 0; }
.body { background-color: #f4f5f6; width: 100%; }
.container { margin: 0 auto !important; max-width: 600px; padding: 0;
padding-top: 24px; width: 600px; }
.content { box-sizing: border-box; display: block; margin: 0 auto;
max-width: 600px; padding: 0; } /* -------------------------------------
HEADER, FOOTER, MAIN ------------------------------------- */
.main { background: #ffffff; border: 1px solid #eaebed; border-radius:
16px; width: 100%; }
.wrapper { box-sizing: border-box; padding: 24px; }
.footer { clear: both; padding-top: 24px; text-align: center; width: 100%;
}
.footer td, .footer p, .footer span, .footer a { color: #9a9ea6;
font-size: 16px; text-align: center; } /*
------------------------------------- TYPOGRAPHY
------------------------------------- */
p { font-family: Helvetica, sans-serif; font-size: 16px; font-weight:
normal; margin: 0; margin-bottom: 16px; }
a { color: #0867ec; text-decoration: underline; } /*
------------------------------------- BUTTONS
------------------------------------- */
.btn { box-sizing: border-box; min-width: 100% !important; width: 100%; }
.btn > tbody > tr > td { padding-bottom: 16px; }
.btn table { width: auto; }
.btn table td { background-color: #ffffff; border-radius: 4px; text-align:
center; }
.btn a { background-color: #ffffff; border: solid 2px #0867ec;
border-radius: 4px; box-sizing: border-box; color: #0867ec; cursor:
pointer; display: inline-block; font-size: 16px; font-weight: bold;
margin: 0; padding: 12px 24px; text-decoration: none; text-transform:
capitalize; }
.btn-primary table td { background-color: #0867ec; }
.btn-primary a { background-color: #0867ec; border-color: #0867ec; color:
#ffffff; }
@media all { .btn-primary table td:hover { background-color: #ec0867
!important; } .btn-primary a:hover { background-color: #ec0867 !important;
border-color: #ec0867 !important; } }
/* ------------------------------------- OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last { margin-bottom: 0; }
.first { margin-top: 0; }
.align-center { text-align: center; }
.align-right { text-align: right; }
.align-left { text-align: left; }
.text-link { color: #0867ec !important; text-decoration: underline
!important; }
.clear { clear: both; }
.mt0 { margin-top: 0; }
.mb0 { margin-bottom: 0; }
.preheader { color: transparent; display: none; height: 0; max-height: 0;
max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility:
hidden; width: 0; }
.powered-by a { text-decoration: none; }
/* ------------------------------------- RESPONSIVE AND MOBILE FRIENDLY
STYLES ------------------------------------- */
@media only screen and (max-width: 640px) { .main p, .main td, .main span
{ font-size: 16px !important; } .wrapper { padding: 8px !important; }
.content { padding: 0 !important; } .container { padding: 0 !important;
padding-top: 8px !important; width: 100% !important; } .main {
border-left-width: 0 !important; border-radius: 0 !important;
border-right-width: 0 !important; } .btn table { max-width: 100%
!important; width: 100% !important; } .btn a { font-size: 16px !important;
max-width: 100% !important; width: 100% !important; } } /*
------------------------------------- PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all { .ExternalClass { width: 100%; } .ExternalClass,
.ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass
td, .ExternalClass div { line-height: 100%; } .apple-link a { color:
inherit !important; font-family: inherit !important; font-size: inherit
!important; font-weight: inherit !important; line-height: inherit
!important; text-decoration: none !important; } #MessageViewBody a {
color: inherit; text-decoration: none; font-size: inherit; font-family:
inherit; font-weight: inherit; line-height: inherit; } }
</style>
</head>
<body>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
class="body"
>
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">Nouveau message de contact</span>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
class="main"
>
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<p>
{{ data.lastName }} {{ data.firstName }}
</p>
{% if data.company %}
<p>
{{ data.company }}
</p>
{% endif %}
<p>
{{ data.message }}
</p>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
class="btn btn-primary"
>
<tbody>
<tr>
<td align="left">
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
>
<tbody>
<tr>
<td>
<a
href="https://arts-ticule.fr"
target="_blank"
>
Voir le site
</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p>
Mon adresse électronique : {{ data.email }}
</p>
{% if data.phoneNumber %}
<p>
Mes coordonnées téléphoniques : {{ data.phoneNumber }}
</p>
{% endif %}
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
>
<tr>
<td class="content-block">
<span class="apple-link">Arts-Ticule</span>
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,311 @@
{% extends 'base.html.twig' %}
{% block metadata %}
{# ============================
METADATA / SEO
============================ #}
{% set meta_title = meta_title is defined
? meta_title
: 'Contact Spectacles vivants et artisanat pour collectivités'
%}
{% set meta_description = meta_description is defined
? meta_description
: 'Contactez-nous pour vos projets de spectacles vivants et de créations artisanales destinés aux collectivités, écoles et lieux culturels.'
%}
{% set meta_image = meta_image is defined
? meta_image
: asset('images/logo.jpg')
%}
{% set meta_url = meta_url is defined ? meta_url : app.request.uri %}
{% set meta_site_name = meta_site_name is defined
? meta_site_name
: 'Nom du site'
%}
{% set meta_type = meta_type is defined ? meta_type : 'website' %}
<title>
{{ meta_title }}
</title>
<meta name="description" content="{{ meta_description }}" />
<meta name="robots" content="index, follow" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link rel="canonical" href="{{ meta_url }}" />
{# ============================
OPEN GRAPH (Facebook, LinkedIn…)
============================ #}
<meta property="og:title" content="{{ meta_title }}" />
<meta property="og:description" content="{{ meta_description }}" />
<meta property="og:type" content="{{ meta_type }}" />
<meta property="og:url" content="{{ meta_url }}" />
<meta property="og:image" content="{{ meta_image }}" />
<meta property="og:site_name" content="{{ meta_site_name }}" />
<meta property="og:locale" content="fr_FR" />
{# Optionnel mais recommandé #}
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
{# ============================
TWITTER CARDS
============================ #}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ meta_title }}" />
<meta name="twitter:description" content="{{ meta_description }}" />
<meta name="twitter:image" content="{{ meta_image }}" />
{% endblock %}
{% block body %}
<div class="isolate bg-white px-6 py-24 sm:py-32 lg:px-8">
<div
aria-hidden="true"
class="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80"
>
<div
style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)"
class="relative left-1/2 -z-10 aspect-1155/678 w-144.5 max-w-none -translate-x-1/2 rotate-30 bg-linear-to-tr from-[#ff80b5] to-[#F59E0B] opacity-30 sm:left-[calc(50%-40rem)] sm:w-288.75"
></div>
</div>
<div class="mx-auto max-w-2xl text-center">
<h2
class="text-4xl font-semibold tracking-tight text-balance text-gray-900 sm:text-5xl"
>
Contactez moi
</h2>
<p class="mt-2 text-lg/8 text-gray-600">
Vous pouvez me contacter via le formulaire ci-dessous et je vous
répondrai rapidement.
</p>
{% for label, messages in app.flashes %}
{% for message in messages %}
<div
class="mb-6 rounded-md p-4 text-sm font-medium {% if label == 'success' %} bg-green-50 text-green-800 {% elseif label == 'error' %} bg-red-50 text-red-800 {% elseif label == 'warning' %} bg-yellow-50 text-yellow-800 {% endif %}"
>
{{ message }}
</div>
{% endfor %}
{% endfor %}
</div>
{{
form_start(
form,
{
attr: {
class: 'mx-auto mt-16 max-w-xl sm:mt-20'
}
}
)
}}
<div class="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
<div>
<label
for="{{ form.lastName.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Nom
</label>
<div class="mt-2.5">
{{
form_widget(
form.lastName,
{
attr: {
placeholder: 'Votre nom',
autocomplete: 'family-name',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div>
<label
for="{{ form.firstName.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Prénom
</label>
<div class="mt-2.5">
{{
form_widget(
form.firstName,
{
attr: {
placeholder: 'Votre prénom',
autocomplete: 'given-name',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div class="sm:col-span-2">
<label
for="{{ form.company.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Entreprise
</label>
<div class="mt-2.5">
{{
form_widget(
form.company,
{
attr: {
placeholder: 'Raison sociale',
autocomplete: 'organization',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div class="sm:col-span-2">
<label
for="{{ form.email.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Email
</label>
<div class="mt-2.5">
{{
form_widget(
form.email,
{
attr: {
placeholder: 'Votre adresse email',
autocomplete: 'email',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div class="sm:col-span-2">
<label
for="{{ form.phoneNumber.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Numéro de téléphone
</label>
<div class="mt-2.5">
{{
form_widget(
form.phoneNumber,
{
attr: {
placeholder: 'Votre numéro de téléphone',
autocomplete: 'tel',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div class="sm:col-span-2 hidden">
<label
for="{{ form.phoneNumber.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Numéro de téléphone mobile
</label>
<div class="mt-2.5">
{{
form_widget(
form.mobilePhoneNumber,
{
attr: {
placeholder: 'Votre numéro de téléphone',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div class="sm:col-span-2">
<label
for="{{ form.message.vars.id }}"
class="block text-sm/6 font-semibold text-gray-900"
>
Message
</label>
<div class="mt-2.5">
{{
form_widget(
form.message,
{
attr: {
rows: 4,
placeholder: 'Votre message',
class: 'block w-full rounded-md bg-white px-3.5 py-2 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-amber-600'
}
}
)
}}
</div>
</div>
<div class="flex gap-x-4 sm:col-span-2">
<div class="flex h-6 items-center">
<div
class="group relative inline-flex w-8 shrink-0 rounded-full bg-gray-200 p-px inset-ring inset-ring-gray-900/5 outline-offset-2 outline-amber-600 transition-colors duration-200 ease-in-out has-checked:bg-amber-600 has-focus-visible:outline-2"
>
<span
class="size-4 rounded-full bg-white shadow-xs ring-1 ring-gray-900/5 transition-transform duration-200 ease-in-out group-has-checked:translate-x-3.5"
>
</span>
{{
form_widget(
form.agreeToPolicies,
{
attr: {
class: 'absolute inset-0 size-full appearance-none focus:outline-hidden'
}
}
)
}}
</div>
</div>
<label
for="{{ form.agreeToPolicies.vars.id }}"
class="text-sm/6 text-gray-600"
>
En cochant cette case, vous acceptez notre
<a
href="{{ path('confidentialitypolicy') }}"
class="font-semibold whitespace-nowrap text-amber-600"
>
politique de confidentialité
</a>.
</label>
</div>
</div>
<div class="mt-10">
<button
type="submit"
class="block w-full rounded-md bg-amber-600 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-xs hover:bg-amber-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600"
>
Envoyer ma demande
</button>
</div>
{{ form_end(form) }}
</div>
{% endblock %}