diff --git a/assets/styles/app.css b/assets/styles/app.css index 35d653e..02f0d8b 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -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"; diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 71bce7d..b454750 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -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 diff --git a/migrations/Version20260117031003.php b/migrations/Version20260117031003.php new file mode 100644 index 0000000..6e53ff7 --- /dev/null +++ b/migrations/Version20260117031003.php @@ -0,0 +1,31 @@ +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'); + } +} diff --git a/migrations/Version20260117033945.php b/migrations/Version20260117033945.php new file mode 100644 index 0000000..79914e6 --- /dev/null +++ b/migrations/Version20260117033945.php @@ -0,0 +1,31 @@ +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'); + } +} diff --git a/src/Controller/ConfigurationController.php b/src/Controller/ConfigurationController.php new file mode 100644 index 0000000..69264b6 --- /dev/null +++ b/src/Controller/ConfigurationController.php @@ -0,0 +1,43 @@ +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(), + ]); + } +} diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index eadddcc..fa13bc3 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -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 l’envoi 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 { diff --git a/src/Entity/Configuration.php b/src/Entity/Configuration.php new file mode 100644 index 0000000..2b2327b --- /dev/null +++ b/src/Entity/Configuration.php @@ -0,0 +1,35 @@ +id; + } + + public function getOwnerMail(): ?string + { + return $this->ownerMail; + } + + public function setOwnerMail(?string $ownerMail): static + { + $this->ownerMail = $ownerMail; + + return $this; + } +} diff --git a/src/Entity/Contact.php b/src/Entity/Contact.php new file mode 100644 index 0000000..229773a --- /dev/null +++ b/src/Entity/Contact.php @@ -0,0 +1,50 @@ +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; + } +} diff --git a/src/Form/ConfigurationType.php b/src/Form/ConfigurationType.php new file mode 100644 index 0000000..4352f40 --- /dev/null +++ b/src/Form/ConfigurationType.php @@ -0,0 +1,44 @@ +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, + ]); + } +} diff --git a/src/Form/ContactType.php b/src/Form/ContactType.php new file mode 100644 index 0000000..c8da7f0 --- /dev/null +++ b/src/Form/ContactType.php @@ -0,0 +1,47 @@ +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()], + ]) + ; + } +} diff --git a/src/Form/NewsType.php b/src/Form/NewsType.php index 53b5014..71aa0c3 100644 --- a/src/Form/NewsType.php +++ b/src/Form/NewsType.php @@ -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, diff --git a/src/Repository/ConfigurationRepository.php b/src/Repository/ConfigurationRepository.php new file mode 100644 index 0000000..48b36d1 --- /dev/null +++ b/src/Repository/ConfigurationRepository.php @@ -0,0 +1,43 @@ + + */ +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() + // ; + // } +} diff --git a/src/Repository/ContactRepository.php b/src/Repository/ContactRepository.php new file mode 100644 index 0000000..4944392 --- /dev/null +++ b/src/Repository/ContactRepository.php @@ -0,0 +1,43 @@ + + */ +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() + // ; + // } +} diff --git a/src/Service/MailService.php b/src/Service/MailService.php new file mode 100644 index 0000000..d28ebbe --- /dev/null +++ b/src/Service/MailService.php @@ -0,0 +1,36 @@ +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); + } +} diff --git a/templates/base-admin/sidebar.html.twig b/templates/base-admin/sidebar.html.twig index 88ca0e7..84430a9 100644 --- a/templates/base-admin/sidebar.html.twig +++ b/templates/base-admin/sidebar.html.twig @@ -315,6 +315,19 @@ Fonctionnalité +
  • + + + C + + Configuration + +
  • {% endif %} diff --git a/templates/base/header.html.twig b/templates/base/header.html.twig index d465ae0..5223c29 100644 --- a/templates/base/header.html.twig +++ b/templates/base/header.html.twig @@ -64,8 +64,8 @@ Me connaître Me contacter diff --git a/templates/configuration/index.html.twig b/templates/configuration/index.html.twig new file mode 100644 index 0000000..618b58d --- /dev/null +++ b/templates/configuration/index.html.twig @@ -0,0 +1,27 @@ +{% extends 'base_admin.html.twig' %} + +{% block title %} + Configuration +{% endblock %} + +{% block body %} +
    +
    +
    + {{ form_start(form) }} + {{ form_widget(form) }} + {{ form_end(form) }} +
    +
    +
    + +{% endblock %} diff --git a/templates/form/custom_tailwind_theme.html.twig b/templates/form/custom_tailwind_theme.html.twig deleted file mode 100644 index 20ed0bd..0000000 --- a/templates/form/custom_tailwind_theme.html.twig +++ /dev/null @@ -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 -%} diff --git a/templates/mail/contact.html.twig b/templates/mail/contact.html.twig new file mode 100644 index 0000000..1238f20 --- /dev/null +++ b/templates/mail/contact.html.twig @@ -0,0 +1,238 @@ + + + + + + + Nouveau message de contact + + + + + + + + + + + + + diff --git a/templates/page/contact.html.twig b/templates/page/contact.html.twig new file mode 100644 index 0000000..d20fed6 --- /dev/null +++ b/templates/page/contact.html.twig @@ -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' %} + + + {{ meta_title }} + + + + + + + + + + {# ============================ + OPEN GRAPH (Facebook, LinkedIn…) + ============================ #} + + + + + + + + + + {# Optionnel mais recommandé #} + + + + {# ============================ + TWITTER CARDS + ============================ #} + + + + + +{% endblock %} +{% block body %} +
    + +
    +

    + Contactez moi +

    +

    + Vous pouvez me contacter via le formulaire ci-dessous et je vous + répondrai rapidement. +

    + {% for label, messages in app.flashes %} + {% for message in messages %} +
    + {{ message }} +
    + {% endfor %} + {% endfor %} +
    + {{ + form_start( + form, + { + attr: { + class: 'mx-auto mt-16 max-w-xl sm:mt-20' + } + } + ) + }} +
    +
    + +
    + {{ + 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' + } + } + ) + }} +
    +
    +
    + +
    + {{ + 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' + } + } + ) + }} +
    +
    + +
    + +
    + {{ + 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' + } + } + ) + }} +
    +
    + +
    + +
    + {{ + 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' + } + } + ) + }} +
    +
    + +
    + +
    + {{ + 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' + } + } + ) + }} +
    +
    + + + +
    + +
    + {{ + 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' + } + } + ) + }} +
    +
    + +
    +
    +
    + + + + + {{ + form_widget( + form.agreeToPolicies, + { + attr: { + class: 'absolute inset-0 size-full appearance-none focus:outline-hidden' + } + } + ) + }} +
    +
    + + +
    +
    +
    + +
    + {{ form_end(form) }} +
    +{% endblock %}