Add registration page with buyer/organizer tabs
- Add organizer fields to User entity: companyName, siret, address, postalCode, city, phone - Update RegistrationController to handle buyer and organizer registration types - Create register template with tab selector (acheteur/organisateur) - Organizer registration assigns ROLE_ORGANIZER - Add migration for new User fields Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
migrations/Version20260319102614.php
Normal file
41
migrations/Version20260319102614.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?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 Version20260319102614 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('ALTER TABLE "user" ADD company_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE "user" ADD siret VARCHAR(14) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE "user" ADD address VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE "user" ADD postal_code VARCHAR(10) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE "user" ADD city VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE "user" ADD phone VARCHAR(20) DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE "user" DROP company_name');
|
||||||
|
$this->addSql('ALTER TABLE "user" DROP siret');
|
||||||
|
$this->addSql('ALTER TABLE "user" DROP address');
|
||||||
|
$this->addSql('ALTER TABLE "user" DROP postal_code');
|
||||||
|
$this->addSql('ALTER TABLE "user" DROP city');
|
||||||
|
$this->addSql('ALTER TABLE "user" DROP phone');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|||||||
|
|
||||||
class RegistrationController extends AbstractController
|
class RegistrationController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route('/inscription', name: 'app_register')]
|
#[Route('/inscription', name: 'app_register', methods: ['GET', 'POST'])]
|
||||||
public function register(
|
public function register(
|
||||||
Request $request,
|
Request $request,
|
||||||
UserPasswordHasherInterface $passwordHasher,
|
UserPasswordHasherInterface $passwordHasher,
|
||||||
@@ -25,14 +25,25 @@ class RegistrationController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
|
$type = $request->request->getString('type');
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setFirstName($request->request->getString('first_name'));
|
$user->setFirstName(trim($request->request->getString('first_name')));
|
||||||
$user->setLastName($request->request->getString('last_name'));
|
$user->setLastName(trim($request->request->getString('last_name')));
|
||||||
$user->setEmail($request->request->getString('email'));
|
$user->setEmail(trim($request->request->getString('email')));
|
||||||
|
|
||||||
$plainPassword = $request->request->getString('password');
|
$plainPassword = $request->request->getString('password');
|
||||||
$user->setPassword($passwordHasher->hashPassword($user, $plainPassword));
|
$user->setPassword($passwordHasher->hashPassword($user, $plainPassword));
|
||||||
|
|
||||||
|
if ('organizer' === $type) {
|
||||||
|
$user->setRoles(['ROLE_ORGANIZER']);
|
||||||
|
$user->setCompanyName(trim($request->request->getString('company_name')));
|
||||||
|
$user->setSiret(trim($request->request->getString('siret')));
|
||||||
|
$user->setAddress(trim($request->request->getString('address')));
|
||||||
|
$user->setPostalCode(trim($request->request->getString('postal_code')));
|
||||||
|
$user->setCity(trim($request->request->getString('city')));
|
||||||
|
$user->setPhone(trim($request->request->getString('phone')));
|
||||||
|
}
|
||||||
|
|
||||||
$errors = $validator->validate($user);
|
$errors = $validator->validate($user);
|
||||||
if (0 === count($errors)) {
|
if (0 === count($errors)) {
|
||||||
$em->persist($user);
|
$em->persist($user);
|
||||||
@@ -47,6 +58,11 @@ class RegistrationController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('security/register.html.twig');
|
return $this->render('security/register.html.twig', [
|
||||||
|
'breadcrumbs' => [
|
||||||
|
['name' => 'Accueil', 'url' => '/'],
|
||||||
|
['name' => 'Inscription', 'url' => '/inscription'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\Column(length: 255, unique: true, nullable: true)]
|
#[ORM\Column(length: 255, unique: true, nullable: true)]
|
||||||
private ?string $keycloakId = null;
|
private ?string $keycloakId = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $companyName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 14, nullable: true)]
|
||||||
|
private ?string $siret = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $address = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 10, nullable: true)]
|
||||||
|
private ?string $postalCode = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $city = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 20, nullable: true)]
|
||||||
|
private ?string $phone = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private \DateTimeImmutable $createdAt;
|
private \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
@@ -125,6 +143,78 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCompanyName(): ?string
|
||||||
|
{
|
||||||
|
return $this->companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCompanyName(?string $companyName): static
|
||||||
|
{
|
||||||
|
$this->companyName = $companyName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSiret(): ?string
|
||||||
|
{
|
||||||
|
return $this->siret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSiret(?string $siret): static
|
||||||
|
{
|
||||||
|
$this->siret = $siret;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAddress(): ?string
|
||||||
|
{
|
||||||
|
return $this->address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAddress(?string $address): static
|
||||||
|
{
|
||||||
|
$this->address = $address;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPostalCode(): ?string
|
||||||
|
{
|
||||||
|
return $this->postalCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPostalCode(?string $postalCode): static
|
||||||
|
{
|
||||||
|
$this->postalCode = $postalCode;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCity(): ?string
|
||||||
|
{
|
||||||
|
return $this->city;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCity(?string $city): static
|
||||||
|
{
|
||||||
|
$this->city = $city;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPhone(): ?string
|
||||||
|
{
|
||||||
|
return $this->phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPhone(?string $phone): static
|
||||||
|
{
|
||||||
|
$this->phone = $phone;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getKeycloakId(): ?string
|
public function getKeycloakId(): ?string
|
||||||
{
|
{
|
||||||
return $this->keycloakId;
|
return $this->keycloakId;
|
||||||
|
|||||||
@@ -1,7 +1,181 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Inscription - E-Ticket{% endblock %}
|
{% block title %}Inscription - E-Ticket{% endblock %}
|
||||||
|
{% block description %}Creez votre compte E-Ticket en tant qu'acheteur ou organisateur d'evenements{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
<div style="max-width:36rem;margin:0 auto;padding:3rem 1rem;">
|
||||||
|
<h1 class="text-3xl font-black uppercase tracking-tighter italic" style="border-bottom:4px solid #111827;display:inline-block;margin-bottom:0.5rem;">Inscription</h1>
|
||||||
|
<p class="font-bold text-gray-600 italic" style="margin-bottom:2rem;">Creez votre compte.</p>
|
||||||
|
|
||||||
|
{% for message in app.flashes('success') %}
|
||||||
|
<div style="border:4px solid #111827;padding:1rem 1.5rem;margin-bottom:2rem;background:#d1fae5;box-shadow:4px 4px 0 rgba(0,0,0,1);">
|
||||||
|
<p class="font-black text-sm">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for message in app.flashes('error') %}
|
||||||
|
<div style="border:4px solid #111827;padding:1rem 1.5rem;margin-bottom:2rem;background:#fee2e2;box-shadow:4px 4px 0 rgba(0,0,0,1);">
|
||||||
|
<p class="font-black text-sm">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div style="display:flex;gap:0;margin-bottom:2rem;">
|
||||||
|
<button data-tab="tab-buyer" type="button"
|
||||||
|
style="flex:1;padding:0.75rem;border:3px solid #111827;border-right:none;cursor:pointer;background:#111827;color:white;"
|
||||||
|
class="font-black uppercase text-sm tracking-widest transition-all">
|
||||||
|
Acheteur
|
||||||
|
</button>
|
||||||
|
<button data-tab="tab-organizer" type="button"
|
||||||
|
style="flex:1;padding:0.75rem;border:3px solid #111827;cursor:pointer;background:white;color:#111827;"
|
||||||
|
class="font-black uppercase text-sm tracking-widest transition-all">
|
||||||
|
Organisateur
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-buyer" style="display:block;">
|
||||||
|
<form method="post" action="{{ path('app_register') }}" style="display:flex;flex-direction:column;gap:1.5rem;">
|
||||||
|
<input type="hidden" name="type" value="buyer">
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;">
|
||||||
|
<div style="flex:1;min-width:150px;">
|
||||||
|
<label for="buyer_last_name" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Nom</label>
|
||||||
|
<input type="text" id="buyer_last_name" name="last_name" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="Dupont">
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;min-width:150px;">
|
||||||
|
<label for="buyer_first_name" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Prenom</label>
|
||||||
|
<input type="text" id="buyer_first_name" name="first_name" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="Jean">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="buyer_email" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Email</label>
|
||||||
|
<input type="email" id="buyer_email" name="email" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="jean.dupont@exemple.fr">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="buyer_password" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Mot de passe</label>
|
||||||
|
<input type="password" id="buyer_password" name="password" required minlength="8"
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="••••••••">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit"
|
||||||
|
style="width:100%;padding:0.75rem 2rem;border:3px solid #111827;box-shadow:4px 4px 0 rgba(0,0,0,1);cursor:pointer;"
|
||||||
|
class="bg-yellow-400 font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
||||||
|
Creer mon compte
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-organizer" style="display:none;">
|
||||||
|
<form method="post" action="{{ path('app_register') }}" style="display:flex;flex-direction:column;gap:1.5rem;">
|
||||||
|
<input type="hidden" name="type" value="organizer">
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;">
|
||||||
|
<div style="flex:1;min-width:150px;">
|
||||||
|
<label for="orga_last_name" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Nom</label>
|
||||||
|
<input type="text" id="orga_last_name" name="last_name" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="Dupont">
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;min-width:150px;">
|
||||||
|
<label for="orga_first_name" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Prenom</label>
|
||||||
|
<input type="text" id="orga_first_name" name="first_name" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="Jean">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="orga_company" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Raison sociale / Nom de l'association</label>
|
||||||
|
<input type="text" id="orga_company" name="company_name" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="Mon association">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="orga_siret" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">SIRET</label>
|
||||||
|
<input type="text" id="orga_siret" name="siret" required pattern="[0-9]{14}" maxlength="14"
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="12345678901234">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="orga_email" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Email</label>
|
||||||
|
<input type="email" id="orga_email" name="email" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="contact@association.fr">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="orga_address" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Adresse</label>
|
||||||
|
<input type="text" id="orga_address" name="address" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="12 rue de la Paix">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;">
|
||||||
|
<div style="flex:1;min-width:120px;">
|
||||||
|
<label for="orga_postal" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Code postal</label>
|
||||||
|
<input type="text" id="orga_postal" name="postal_code" required pattern="[0-9]{5}" maxlength="5"
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="75001">
|
||||||
|
</div>
|
||||||
|
<div style="flex:2;min-width:150px;">
|
||||||
|
<label for="orga_city" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Ville</label>
|
||||||
|
<input type="text" id="orga_city" name="city" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="Paris">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="orga_phone" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Telephone</label>
|
||||||
|
<input type="tel" id="orga_phone" name="phone" required
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="06 12 34 56 78">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="orga_password" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Mot de passe</label>
|
||||||
|
<input type="password" id="orga_password" name="password" required minlength="8"
|
||||||
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
||||||
|
class="focus:border-indigo-600"
|
||||||
|
placeholder="••••••••">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit"
|
||||||
|
style="width:100%;padding:0.75rem 2rem;border:3px solid #111827;box-shadow:4px 4px 0 rgba(0,0,0,1);cursor:pointer;"
|
||||||
|
class="bg-yellow-400 font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
||||||
|
Creer mon compte organisateur
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:2rem;text-align:center;">
|
||||||
|
<p class="text-sm font-bold text-gray-600">Deja un compte ? <a href="{{ path('app_login') }}" class="text-indigo-600 hover:underline font-black">Connexion</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user