```
✨ feat(all): Ajoute l'inscription, Turnstile, Sentry et améliore l'EPAGE en français.
```
This commit is contained in:
9
.env
9
.env
@@ -69,3 +69,12 @@ GOOGLE_APPLICATION_CREDENTIALS=%kernel.project_dir%/google.json
|
||||
###> sentry/sentry-symfony ###
|
||||
SENTRY_DSN="https://4f43769e7c483f14da26e05824a482d0@o4509563601092608.ingest.de.sentry.io/4510392636473424"
|
||||
###< sentry/sentry-symfony ###
|
||||
|
||||
###> pixelopen/cloudflare-turnstile-bundle ###
|
||||
TURNSTILE_KEY=3x00000000000000000000FF
|
||||
TURNSTILE_SECRET=1x0000000000000000000000000000000AA
|
||||
###< pixelopen/cloudflare-turnstile-bundle ###
|
||||
|
||||
###> stripe/stripe-php ###
|
||||
STRIPE_SECRET_KEY=sk_test_***
|
||||
###< stripe/stripe-php ###
|
||||
|
||||
@@ -74,7 +74,9 @@
|
||||
STRIPE_SK=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
|
||||
STRIPE_WEBHOOKS_SIGN=whsec_wNHtgjypqbfP7erAqifCOzZvW8kW9oB7
|
||||
MAILER_DSN=ses+smtp://AKIAWTT2T22CWBRBBDYN:BBdgb6KxRQ8mNcpWFJsZCJxbSGNdgLhKFiITMErfBlQP@default?region=eu-west-3
|
||||
SENTRY_DSN="https://4f43769e7c483f14da26e05824a482d0@o4509563601092608.ingest.de.sentry.io/4510392636473424"
|
||||
SENTRY_DSN="https://375cf73e411fb1aa515202b7922cbaeb@sentry.esy-web.dev/6"
|
||||
TURNSTILE_KEY=0x4AAAAAACI84gZ0CLCEZY5i
|
||||
TURNSTILE_SECRET=0x4AAAAAACI84k8G11ODrOwCNAaWyWQ_Vzk
|
||||
dest: "{{ path }}/.env.local"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ www.e-cosplay.fr {
|
||||
request_body {
|
||||
max_size 100MB
|
||||
}
|
||||
handle_path /sc.js {
|
||||
redir https://sentry.esy-web.dev/js-sdk-loader/49feadbadd9b443832205c4eeac1c4f5.min.js
|
||||
}
|
||||
handle_path /ts.js {
|
||||
redir https://widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import './app.scss'
|
||||
import * as Turbo from "@hotwired/turbo"
|
||||
|
||||
import '@grafikart/drop-files-element'
|
||||
import {PaymentForm} from './PaymentForm'
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
// --- CLÉS DE STOCKAGE ET VAPID ---
|
||||
const VAPID_PUBLIC_KEY = "BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo";
|
||||
@@ -482,6 +483,7 @@ document.addEventListener('turbo:load', () => {
|
||||
initializeUI();
|
||||
handleNotificationBanner();
|
||||
handleCookieBanner(); // Doit être appelé à chaque chargement Turbo
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
@import url('https://fonts.googleapis.com/css2?family=Intel+One+Mono:ital,wght@0,300..700;1,300..700&display=swap');
|
||||
|
||||
.bg-op {
|
||||
background: rgba(0,0,0,0.5);
|
||||
backdrop-filter: blur(5px);
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"phpdocumentor/reflection-docblock": "^5.6.4",
|
||||
"phpoffice/phpspreadsheet": ">=5.3",
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
"pixelopen/cloudflare-turnstile-bundle": "^0.4.1",
|
||||
"presta/sitemap-bundle": "^4.2",
|
||||
"sentry/sentry-symfony": "^5.6",
|
||||
"setasign/fpdi": "^2.6.4",
|
||||
|
||||
61
composer.lock
generated
61
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f27062872645f8d1a8553efdd75c2aef",
|
||||
"content-hash": "ed26fccb39be0438a7def45a9b987dc9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -6899,6 +6899,65 @@
|
||||
},
|
||||
"time": "2025-08-30T15:50:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pixelopen/cloudflare-turnstile-bundle",
|
||||
"version": "0.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Pixel-Open/cloudflare-turnstile-bundle.git",
|
||||
"reference": "d000eb4b37c48f9421fb603742e7e23611204182"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Pixel-Open/cloudflare-turnstile-bundle/zipball/d000eb4b37c48f9421fb603742e7e23611204182",
|
||||
"reference": "d000eb4b37c48f9421fb603742e7e23611204182",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"symfony/form": "^5.0|^6.0|^7.0",
|
||||
"symfony/framework-bundle": "^5.0|^6.0|^7.0",
|
||||
"symfony/http-client": "^5.0|^6.0|^7.0",
|
||||
"symfony/twig-bundle": "^5.0|^6.0|^7.0",
|
||||
"symfony/validator": "^5.0|^6.0|^7.0",
|
||||
"symfony/yaml": "^5.0|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpstan/phpstan-symfony": "^1.2",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"rector/rector": "^0.14.5",
|
||||
"symplify/easy-coding-standard": "^11.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PixelOpen\\CloudflareTurnstileBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Pixel Open Team",
|
||||
"homepage": "https://pixel-open.org/projects/symfony-bundle-cloudflare-turnstile/"
|
||||
}
|
||||
],
|
||||
"description": "A simple package to help integrate Cloudflare Turnstile on Symfony.",
|
||||
"keywords": [
|
||||
"Symfony Bundle",
|
||||
"captcha",
|
||||
"cloudflare-turnstile",
|
||||
"form security"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Pixel-Open/cloudflare-turnstile-bundle/issues",
|
||||
"source": "https://github.com/Pixel-Open/cloudflare-turnstile-bundle/tree/0.4.1"
|
||||
},
|
||||
"time": "2024-09-30T15:42:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "presta/sitemap-bundle",
|
||||
"version": "v4.2.0",
|
||||
|
||||
@@ -18,4 +18,5 @@ return [
|
||||
Presta\SitemapBundle\PrestaSitemapBundle::class => ['all' => true],
|
||||
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
|
||||
Sentry\SentryBundle\SentryBundle::class => ['prod' => true],
|
||||
PixelOpen\CloudflareTurnstileBundle\PixelOpenCloudflareTurnstileBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
@@ -74,6 +74,14 @@ vich_uploader:
|
||||
inject_on_load: true
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
epage_avatar:
|
||||
uri_prefix: /epage_avatar/events
|
||||
upload_destination: '%kernel.project_dir%/public/storage/epage_avatar'
|
||||
namer: App\VichUploader\Namer\Epage\AvatarNamer # Replaced namer
|
||||
directory_namer: App\VichUploader\DirectoryNamer\Epage\DirectoryNamer
|
||||
inject_on_load: true
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
#mappings:
|
||||
# products:
|
||||
# uri_prefix: /images/products
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ./docker/caddy/Caddyfile
|
||||
# ./docker/caddy/Caddyfile
|
||||
|
||||
# Nous écoutons sur le port 80 (qui est mappé depuis le port 8000 de l'hôte)
|
||||
:80 {
|
||||
@@ -30,6 +30,9 @@
|
||||
output stdout
|
||||
format json
|
||||
}
|
||||
handle_path /sc.js {
|
||||
redir https://sentry.esy-web.dev/js-sdk-loader/49feadbadd9b443832205c4eeac1c4f5.min.js
|
||||
}
|
||||
handle_path /ts.js {
|
||||
redir https://widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@grafikart/drop-files-element": "^1.0.9",
|
||||
"@hotwired/turbo": "^8.0.20",
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
"@sentry/browser": "^10.32.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"body-scroll-lock": "^4.0.0-beta.0",
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Entity\Ag\MainOrder;
|
||||
use App\Entity\Ag\MainSigned;
|
||||
use App\Entity\Ag\MainVote;
|
||||
use App\Entity\Event;
|
||||
use App\Entity\InviteEPage;
|
||||
use App\Entity\Members;
|
||||
use App\Entity\MembersCotisations;
|
||||
use App\Entity\Products;
|
||||
@@ -25,6 +26,7 @@ use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Repository\Ag\MainRepository;
|
||||
use App\Repository\EventRepository;
|
||||
use App\Repository\InviteEPageRepository;
|
||||
use App\Repository\MembersCotisationsRepository;
|
||||
use App\Repository\MembersRepository;
|
||||
use App\Repository\ProductsRepository;
|
||||
@@ -286,7 +288,13 @@ class AdminController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
#[Route(path: '/admin/epage/invitation', name: 'admin_epage_invite', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function adminEpageInvitation(InviteEPageRepository $inviteEPageRepository): Response
|
||||
{
|
||||
return $this->render('admin/epage/invitation.twig', [
|
||||
'invitations' => $inviteEPageRepository->findBy([],['id' => 'DESC']),
|
||||
]);
|
||||
}
|
||||
#[Route(path: '/admin/ag', name: 'admin_ag', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function adminAg(Mailer $mailer,UploaderHelper $uploaderHelper,KernelInterface $kernel,EntityManagerInterface $entityManager,Request $request,MainRepository $mainRepository): Response
|
||||
{
|
||||
|
||||
@@ -53,8 +53,8 @@ class HomeController extends AbstractController
|
||||
'city' => $city,
|
||||
]);
|
||||
}
|
||||
const SENTRY_HOST = 'o4509563601092608.ingest.de.sentry.io';
|
||||
const SENTRY_PROJECT_IDS = ['4510392640340048'];
|
||||
const SENTRY_HOST = 'sentry.esy-web.dev';
|
||||
const SENTRY_PROJECT_IDS = ['7'];
|
||||
|
||||
#[Route('/uptime',name: 'app_uptime',options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function app_uptime(Request $request,HttpClientInterface $httpClient): Response
|
||||
|
||||
43
src/Controller/JoinController.php
Normal file
43
src/Controller/JoinController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Dto\Contact\ContactType;
|
||||
use App\Dto\Contact\DtoContact;
|
||||
use App\Dto\Join\JoinType;
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Entity\Join;
|
||||
use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Twig\Environment;
|
||||
|
||||
class JoinController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route(path: '/join', name: 'app_recruit', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function join(Request $request,Mailer $mailer): Response
|
||||
{
|
||||
$j= new Join();
|
||||
$form = $this->createForm(JoinType::class,$j);
|
||||
$form->handleRequest($request);
|
||||
if($form->isSubmitted() && $form->isValid()){
|
||||
|
||||
}
|
||||
return $this->render('join.twig',[
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,16 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\OnBoaringEpage;
|
||||
use App\Form\EPageOnboard;
|
||||
use App\Repository\AbonementsRepository;
|
||||
use Cocur\Slugify\Slugify;
|
||||
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;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class PagesController extends AbstractController
|
||||
{
|
||||
@@ -21,5 +28,88 @@ class PagesController extends AbstractController
|
||||
return $this->render('pages/prestation.twig',[
|
||||
]);
|
||||
}
|
||||
#[Route(path: '/pages/onboaring', name: 'app_pages_onboaring', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function onboaring(Request $request,AbonementsRepository $abonementsRepository,EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
// Définir la liste des abonnements valides.
|
||||
$validAboDurations = ['1M', '2M', '3M', '6M', '12M'];
|
||||
|
||||
// 1. Vérifier si le paramètre 'abo' est présent dans la requête.
|
||||
if (!$request->query->has('abo')) {
|
||||
// Si 'abo' n'est pas présent, rediriger vers la page de découverte ou de tarification.
|
||||
// J'utilise 'app_pages_discover' comme dans votre exemple initial.
|
||||
return $this->redirectToRoute('app_pages_discover');
|
||||
}
|
||||
|
||||
$selectedAbo = $request->query->get('abo');
|
||||
|
||||
// 2. Vérifier si la valeur de 'abo' est valide.
|
||||
if (!in_array($selectedAbo, $validAboDurations, true)) {
|
||||
// Si la valeur n'est pas valide, rediriger vers la page de découverte.
|
||||
return $this->redirectToRoute('app_pages_discover');
|
||||
}
|
||||
|
||||
// --- DÉBUT DE LA LOGIQUE D'AJOUT EN SESSION ---
|
||||
$session = $request->getSession();
|
||||
|
||||
// Vérifier si un UUID de panier (cartId) existe déjà en session
|
||||
if (!$session->has('cart_uuid')) {
|
||||
// Si non, générer un nouvel UUID V4 et le stocker
|
||||
$cartUuid = Uuid::v4()->toString();
|
||||
$session->set('cart_uuid', $cartUuid);
|
||||
|
||||
// Initialiser le panier en session avec l'abonnement sélectionné
|
||||
$session->set('cart_items', [
|
||||
'subscription' => $selectedAbo,
|
||||
'uuid' => $cartUuid,
|
||||
// Vous pouvez ajouter d'autres informations ici (timestamp, etc.)
|
||||
]);
|
||||
|
||||
} else {
|
||||
// Si l'UUID existe déjà, on met juste à jour l'abonnement sélectionné
|
||||
// Si l'on suppose qu'il s'agit d'une mise à jour du choix dans le panier existant.
|
||||
$cartItems = $session->get('cart_items', []);
|
||||
$cartItems['subscription'] = $selectedAbo;
|
||||
$session->set('cart_items', $cartItems);
|
||||
}
|
||||
// --- FIN DE LA LOGIQUE D'AJOUT EN SESSION ---
|
||||
|
||||
$onBoard = new OnBoaringEpage();
|
||||
$onBoard->setState("created");
|
||||
$onBoard->setUuid($session->get('cart_items')['uuid']);
|
||||
if($request->query->get('abo')== "1M")
|
||||
$onBoard->setAbos($abonementsRepository->findOneBy(['name'=>'Epage 1 Mois']));
|
||||
if($request->query->get('abo')== "2M")
|
||||
$onBoard->setAbos($abonementsRepository->findOneBy(['name'=>'Epage 2 Mois']));
|
||||
if($request->query->get('abo')== "3M")
|
||||
$onBoard->setAbos($abonementsRepository->findOneBy(['name'=>'Epage 3 Mois']));
|
||||
if($request->query->get('abo')== "6M")
|
||||
$onBoard->setAbos($abonementsRepository->findOneBy(['name'=>'Epage 6 Mois']));
|
||||
if($request->query->get('abo')== "12M")
|
||||
$onBoard->setAbos($abonementsRepository->findOneBy(['name'=>'Epage 12 Mois']));
|
||||
$onBoard->setUseDomain(false);
|
||||
// Exemple simple : afficher une page de confirmation avec l'abonnement sélectionné.
|
||||
$form = $this->createForm(EPageOnboard::class,$onBoard);
|
||||
$form->handleRequest($request);
|
||||
if($form->isSubmitted() && $form->isValid()){
|
||||
$onBoard->setBirdth($_POST['e_page_onboard']['birdth']);
|
||||
$s = new Slugify();
|
||||
$onBoard->setPageUrl($s->slugify($onBoard->getNameCosplayer()).".e-cosplay.fr");
|
||||
$avatar = $onBoard->getEpage()->getMimeType();
|
||||
if(str_contains($avatar, 'image')){
|
||||
$entityManager->persist($onBoard);
|
||||
$entityManager->flush();
|
||||
|
||||
dd($onBoard);
|
||||
} else {
|
||||
return $this->redirectToRoute('app_pages_onboaring',['type'=>$request->get('type'),'errorFile'=>1]);
|
||||
}
|
||||
}
|
||||
return $this->render('pages/onboarding.twig', [
|
||||
'no_index' => true,
|
||||
'abo' => $selectedAbo,
|
||||
'onBoard' => $onBoard,
|
||||
'form' => $form->createView()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
133
src/Dto/Join/JoinType.php
Normal file
133
src/Dto/Join/JoinType.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto\Join;
|
||||
|
||||
use App\Entity\Join;
|
||||
use PixelOpen\CloudflareTurnstileBundle\Type\TurnstileType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class JoinType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'form.label.name',
|
||||
'required' => true,
|
||||
])
|
||||
->add('surname', TextType::class, [
|
||||
'label' => 'form.label.surname',
|
||||
'required' => true,
|
||||
])
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'form.label.email',
|
||||
'required' => true,
|
||||
])
|
||||
->add('phone', TelType::class, [
|
||||
'label' => 'form.label.phone',
|
||||
'required' => true,
|
||||
])
|
||||
->add('dateBirth', DateTimeType::class, [
|
||||
'label' => 'form.label.birthdate',
|
||||
'widget' => 'single_text',
|
||||
'required' => true,
|
||||
])
|
||||
->add('address', TextType::class, [
|
||||
'label' => 'form.label.address',
|
||||
'required' => true,
|
||||
])
|
||||
->add('zipCode', TextType::class, [
|
||||
'label' => 'form.label.zipcode',
|
||||
'required' => true,
|
||||
])
|
||||
->add('city', TextType::class, [
|
||||
'label' => 'form.label.city',
|
||||
'required' => true,
|
||||
])
|
||||
->add('sexe', ChoiceType::class, [
|
||||
'label' => 'form.label.gender',
|
||||
'choices' => [
|
||||
'not_specified' => 'not_specified',
|
||||
'asexual' => 'asexual',
|
||||
'bisexual' => 'bisexual',
|
||||
'demisexual' => 'demisexual',
|
||||
'gay' => 'gay',
|
||||
'heterosexual' => 'heterosexual',
|
||||
'lesbian' => 'lesbian',
|
||||
'pansexual' => 'pansexual',
|
||||
'queer' => 'queer',
|
||||
'questioning' => 'questioning',
|
||||
'other' => 'other',
|
||||
],
|
||||
'choice_label' => function ($choice) {
|
||||
return 'form.choices.gender.' . $choice;
|
||||
},
|
||||
])
|
||||
->add('pronom', ChoiceType::class, [
|
||||
'label' => 'form.label.pronouns',
|
||||
'choices' => [
|
||||
'il' => 'il',
|
||||
'elle' => 'elle',
|
||||
'iel' => 'iel',
|
||||
'autre' => 'autre',
|
||||
],
|
||||
'choice_label' => function ($choice) {
|
||||
return 'form.choices.pronouns.' . $choice;
|
||||
},
|
||||
])
|
||||
->add('discordAccount', TextType::class, [
|
||||
'label' => 'form.label.discord',
|
||||
'required' => false,
|
||||
])
|
||||
->add('facebookLink', UrlType::class, [
|
||||
'label' => 'form.label.facebook',
|
||||
'required' => false,
|
||||
])
|
||||
->add('instaLink', UrlType::class, [
|
||||
'label' => 'form.label.insta',
|
||||
'required' => false,
|
||||
])
|
||||
->add('tiktokLink', UrlType::class, [
|
||||
'label' => 'form.label.tiktok',
|
||||
'required' => false,
|
||||
])
|
||||
->add('role', ChoiceType::class, [
|
||||
'label' => 'form.label.role',
|
||||
'choices' => [
|
||||
'cosplay' => 'cosplay',
|
||||
'helper' => 'helper',
|
||||
'photographe' => 'photographer',
|
||||
'autre' => 'other',
|
||||
],
|
||||
'choice_label' => function ($choice) {
|
||||
return 'form.choices.role.' . $choice;
|
||||
},
|
||||
'expanded' => true,
|
||||
'multiple' => true,
|
||||
])
|
||||
->add('who', TextareaType::class, [
|
||||
'label' => 'form.label.who',
|
||||
'required' => true,
|
||||
])
|
||||
->add('security', TurnstileType::class, ['attr' => ['data-action' => 'contact', 'data-theme' => 'dark'], 'label' => false])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Join::class,
|
||||
// Permet de s'assurer que les labels sont traduits
|
||||
'translation_domain' => 'messages',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AbonementsRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AbonementsRepository::class)]
|
||||
@@ -40,6 +42,17 @@ class Abonements
|
||||
#[ORM\Column]
|
||||
private ?int $duration = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, OnBoaringEpage>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: OnBoaringEpage::class, mappedBy: 'abos')]
|
||||
private Collection $onBoaringEpages;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->onBoaringEpages = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -152,4 +165,34 @@ class Abonements
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, OnBoaringEpage>
|
||||
*/
|
||||
public function getOnBoaringEpages(): Collection
|
||||
{
|
||||
return $this->onBoaringEpages;
|
||||
}
|
||||
|
||||
public function addOnBoaringEpage(OnBoaringEpage $onBoaringEpage): static
|
||||
{
|
||||
if (!$this->onBoaringEpages->contains($onBoaringEpage)) {
|
||||
$this->onBoaringEpages->add($onBoaringEpage);
|
||||
$onBoaringEpage->setAbos($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeOnBoaringEpage(OnBoaringEpage $onBoaringEpage): static
|
||||
{
|
||||
if ($this->onBoaringEpages->removeElement($onBoaringEpage)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($onBoaringEpage->getAbos() === $this) {
|
||||
$onBoaringEpage->setAbos(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,17 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $isActif = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, OnBoaringEpage>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: OnBoaringEpage::class, mappedBy: 'account')]
|
||||
private Collection $onBoaringEpages;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->accountLoginRegisters = new ArrayCollection();
|
||||
$this->onBoaringEpages = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -332,4 +339,34 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, OnBoaringEpage>
|
||||
*/
|
||||
public function getOnBoaringEpages(): Collection
|
||||
{
|
||||
return $this->onBoaringEpages;
|
||||
}
|
||||
|
||||
public function addOnBoaringEpage(OnBoaringEpage $onBoaringEpage): static
|
||||
{
|
||||
if (!$this->onBoaringEpages->contains($onBoaringEpage)) {
|
||||
$this->onBoaringEpages->add($onBoaringEpage);
|
||||
$onBoaringEpage->setAccount($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeOnBoaringEpage(OnBoaringEpage $onBoaringEpage): static
|
||||
{
|
||||
if ($this->onBoaringEpages->removeElement($onBoaringEpage)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($onBoaringEpage->getAccount() === $this) {
|
||||
$onBoaringEpage->setAccount(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,15 @@ class SitemapSubscriber
|
||||
}
|
||||
$urlContainer->addUrl($decoratedUrlMembers, 'default');
|
||||
|
||||
$urlMembers = new UrlConcrete($urlGenerator->generate('app_recruit', [], UrlGeneratorInterface::ABSOLUTE_URL));
|
||||
$decoratedUrlMembers = new GoogleImageUrlDecorator($urlMembers);
|
||||
$decoratedUrlMembers->addImage(new GoogleImage($this->cacheManager->resolve('assets/images/logo.jpg','webp')));
|
||||
$decoratedUrlMembers = new GoogleMultilangUrlDecorator($decoratedUrlMembers);
|
||||
foreach ($langs as $lang) {
|
||||
$decoratedUrlMembers->addLink($urlGenerator->generate('app_recruit',['lang'=>$lang], UrlGeneratorInterface::ABSOLUTE_URL), $lang);
|
||||
}
|
||||
$urlContainer->addUrl($decoratedUrlMembers, 'default');
|
||||
|
||||
$urlMembers = new UrlConcrete($urlGenerator->generate('app_pages', [], UrlGeneratorInterface::ABSOLUTE_URL));
|
||||
$decoratedUrlMembers = new GoogleImageUrlDecorator($urlMembers);
|
||||
$decoratedUrlMembers->addImage(new GoogleImage($this->cacheManager->resolve('assets/images/logo.jpg','webp')));
|
||||
|
||||
99
src/Form/EPageOnboard.php
Normal file
99
src/Form/EPageOnboard.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\OnBoaringEpage;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class EPageOnboard extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('name',TextType::class,[
|
||||
'label' => 'epage_onboard.name',
|
||||
'required' => true,
|
||||
])
|
||||
->add('surname',TextType::class,[
|
||||
'label' => 'epage_onboard.surname',
|
||||
'required' => true,
|
||||
])
|
||||
->add('email',EmailType::class,[
|
||||
'label' => 'epage_onboard.email',
|
||||
'required' => true,
|
||||
])
|
||||
->add('birdth',DateType::class,[
|
||||
'label' =>'epage_onboard.birdth',
|
||||
'required' => true,
|
||||
'widget' =>'single_text',
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('description',TextareaType::class,[
|
||||
'label' => 'epage_onboard.description',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
'rows' => 5,
|
||||
]
|
||||
])
|
||||
|
||||
->add('nameCosplayer',TextType::class,[
|
||||
'label' => 'epage_onboard.nameCosplayer',
|
||||
'required' => true,
|
||||
])
|
||||
->add('linkInstagram',UrlType::class,[
|
||||
'label' => 'epage_onboard.linkInstagram',
|
||||
'required' => false,
|
||||
])
|
||||
->add('linkFacebook',UrlType::class,[
|
||||
'label' => 'epage_onboard.linkFacebook',
|
||||
'required' => false,
|
||||
])
|
||||
->add('linkTiktok',UrlType::class,[
|
||||
'label' => 'epage_onboard.linkTiktok',
|
||||
'required' => false,
|
||||
])
|
||||
->add('linkX',UrlType::class,[
|
||||
'label' => 'epage_onboard.linkX',
|
||||
'required' => false,
|
||||
])
|
||||
->add('useDomain',CheckboxType::class,[
|
||||
'label' => 'epage_onboard.useDomain',
|
||||
'required' => false,
|
||||
])
|
||||
->add('epage',FileType::class,[
|
||||
'required' => true,
|
||||
'label' => 'epage_onboard.avatar',
|
||||
'attr' =>[
|
||||
'label' => $this->translator->trans('epage_onboard.avatar_label'),
|
||||
'multiple' => false,
|
||||
'accept' => 'image/*',
|
||||
'is' => 'drop-files'
|
||||
]
|
||||
])
|
||||
->add('domain',TextType::class,[
|
||||
'label' => 'epage_onboard.domain',
|
||||
'required' => true,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class',OnBoaringEpage::class);
|
||||
}
|
||||
}
|
||||
19
src/VichUploader/DirectoryNamer/Epage/DirectoryNamer.php
Normal file
19
src/VichUploader/DirectoryNamer/Epage/DirectoryNamer.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\VichUploader\DirectoryNamer\Epage;
|
||||
|
||||
use App\Entity\Account;
|
||||
use App\Entity\OnBoaringEpage;
|
||||
use Vich\UploaderBundle\Mapping\PropertyMapping;
|
||||
use Vich\UploaderBundle\Naming\DirectoryNamerInterface;
|
||||
|
||||
class DirectoryNamer implements DirectoryNamerInterface
|
||||
{
|
||||
|
||||
public function directoryName(object|array $object, PropertyMapping $mapping): string
|
||||
{
|
||||
/** @var OnBoaringEpage $ePage */
|
||||
$ePage = $object;
|
||||
return $ePage->getUuid();
|
||||
}
|
||||
}
|
||||
21
src/VichUploader/Namer/Epage/AvatarNamer.php
Normal file
21
src/VichUploader/Namer/Epage/AvatarNamer.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\VichUploader\Namer\Epage;
|
||||
|
||||
use App\Entity\OnBoaringEpage;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Vich\UploaderBundle\Mapping\PropertyMapping;
|
||||
use Vich\UploaderBundle\Naming\NamerInterface;
|
||||
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
|
||||
class AvatarNamer implements NamerInterface
|
||||
{
|
||||
|
||||
public function name(object $object, PropertyMapping $mapping): string
|
||||
{
|
||||
$file = $mapping->getFile($object);
|
||||
$extension = pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION);
|
||||
return "avatar.".$extension;
|
||||
}
|
||||
}
|
||||
24
symfony.lock
24
symfony.lock
@@ -115,6 +115,18 @@
|
||||
"bin/phpunit"
|
||||
]
|
||||
},
|
||||
"pixelopen/cloudflare-turnstile-bundle": {
|
||||
"version": "0.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "0.1",
|
||||
"ref": "6173b9aff0056dd99ca70a2caa940cc7bcac0b4b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/pixel_open_cloudflare_turnstile.yaml"
|
||||
]
|
||||
},
|
||||
"presta/sitemap-bundle": {
|
||||
"version": "v4.1.3"
|
||||
},
|
||||
@@ -130,6 +142,18 @@
|
||||
"config/packages/sentry.yaml"
|
||||
]
|
||||
},
|
||||
"stripe/stripe-php": {
|
||||
"version": "19.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "19.0",
|
||||
"ref": "d6829c693e3927a8972c7671d74a1a5c505712b0"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/stripe.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/amazon-mailer": {
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
|
||||
@@ -60,6 +60,18 @@ L'overflow-y-auto n'est plus nécessaire ici car c'est le <body> qui gère le sc
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.523 5.754 18 7.5 18s3.332.477 4.5 1.247m0-13C13.168 5.477 14.754 5 16.5 5s3.332.477 4.5 1.253v13C19.832 18.523 18.246 18 16.5 18s-3.332.477-4.5 1.247"></path></svg>
|
||||
AG (Assemblée Générale)
|
||||
</a>
|
||||
<a href="{{ path('admin_ag') }}" class="flex items-center py-2 px-6 text-gray-400 hover:bg-gray-700 hover:text-white transition duration-200 mt-1 rounded-r-lg">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.523 5.754 18 7.5 18s3.332.477 4.5 1.247m0-13C13.168 5.477 14.754 5 16.5 5s3.332.477 4.5 1.253v13C19.832 18.523 18.246 18 16.5 18s-3.332.477-4.5 1.247"></path></svg>
|
||||
EPage - Validation
|
||||
</a>
|
||||
<a href="{{ path('admin_ag') }}" class="flex items-center py-2 px-6 text-gray-400 hover:bg-gray-700 hover:text-white transition duration-200 mt-1 rounded-r-lg">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.523 5.754 18 7.5 18s3.332.477 4.5 1.247m0-13C13.168 5.477 14.754 5 16.5 5s3.332.477 4.5 1.253v13C19.832 18.523 18.246 18 16.5 18s-3.332.477-4.5 1.247"></path></svg>
|
||||
EPage - Cosplayeur(s)
|
||||
</a>
|
||||
<a href="{{ path('admin_epage_invite') }}" class="flex items-center py-2 px-6 text-gray-400 hover:bg-gray-700 hover:text-white transition duration-200 mt-1 rounded-r-lg">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.523 5.754 18 7.5 18s3.332.477 4.5 1.247m0-13C13.168 5.477 14.754 5 16.5 5s3.332.477 4.5 1.253v13C19.832 18.523 18.246 18 16.5 18s-3.332.477-4.5 1.247"></path></svg>
|
||||
EPage - Invitation
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
|
||||
93
templates/admin/epage/invitation.twig
Normal file
93
templates/admin/epage/invitation.twig
Normal file
@@ -0,0 +1,93 @@
|
||||
{% extends 'admin/base.twig' %}
|
||||
|
||||
{% block title %}Ajouter/Éditer un événement{% endblock %}
|
||||
{% block page_title %}
|
||||
Epage - Invitation
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<!-- Conteneur principal: utiliser bg-gray-900 pour l'arrière-plan du corps -->
|
||||
|
||||
<div class="bg-gray-900 text-gray-100 min-h-screen p-6">
|
||||
<!-- Tableau des événements -->
|
||||
<!-- Fond du tableau en gris foncé, ombre conservée -->
|
||||
<div class="bg-gray-800 shadow-xl overflow-hidden sm:rounded-lg">
|
||||
{% if invitations is empty %}
|
||||
<!-- Texte vide en gris clair -->
|
||||
<div class="p-6 text-center text-gray-400">
|
||||
Aucun invitation trouvé. Commencez par en créer un !
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<!-- En-tête du tableau -->
|
||||
<thead class="bg-gray-700">
|
||||
<tr>
|
||||
<!-- Texte de l'en-tête en gris clair/blanc -->
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Titre
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider hidden sm:table-cell">
|
||||
Lieu
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider hidden md:table-cell">
|
||||
Organisateur
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Début
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider hidden md:table-cell">
|
||||
Fin
|
||||
</th>
|
||||
<th scope="col" class="relative px-6 py-3">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- Corps du tableau -->
|
||||
<tbody class="bg-gray-800 divide-y divide-gray-700">
|
||||
{% for event in events %}
|
||||
<!-- Ligne au survol en gris légèrement plus clair -->
|
||||
<tr class="hover:bg-gray-700 transition duration-100">
|
||||
<!-- Titre en blanc -->
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-100">
|
||||
{{ event.title }}
|
||||
</td>
|
||||
<!-- Texte des cellules en gris clair -->
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400 hidden sm:table-cell">
|
||||
{{ event.location }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400 hidden md:table-cell">
|
||||
{{ event.organizer }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">
|
||||
{{ event.startAt|date('d/m/Y') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400 hidden md:table-cell">
|
||||
{{ event.endAt|date('d/m/Y') }}
|
||||
</td>
|
||||
<!-- Actions : lien Modifier en indigo, lien Supprimer en rouge -->
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<a href="{{ path('admin_events_edit', {id: event.id}) }}" class="text-indigo-400 hover:text-indigo-300 mr-3">
|
||||
Modifier
|
||||
</a>
|
||||
<!-- Bouton de suppression -->
|
||||
<form method="POST" action="{{ path('admin_events_delete', {id: event.id}) }}" class="inline-block" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cet événement ? Cette action est irréversible.');">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ event.id) }}">
|
||||
<button type="submit" class="text-red-400 hover:text-red-300 focus:outline-none">
|
||||
Supprimer
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -115,7 +115,19 @@
|
||||
{% if app.environment == "prod" %}
|
||||
<script defer src="https://datas.e-cosplay.fr/vs.js"
|
||||
data-website-id="b929d372-fbea-403e-9ae2-781433828787"></script> {% endif %}
|
||||
|
||||
<script src="/sc.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
Sentry.onLoad(function() {
|
||||
Sentry.init({
|
||||
tunnel:'/tunnel',
|
||||
// Tracing
|
||||
tracesSampleRate: 1.0, // Capture 100% of the transactions
|
||||
// Session Replay
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
{# Le corps aura un fond gris clair pour correspondre au fond du logo #}
|
||||
<body class="flex flex-col min-h-screen bg-gray-100">
|
||||
@@ -128,6 +140,7 @@
|
||||
{ 'name': 'Boutiques'|trans, 'route': 'app_shop' },
|
||||
{ 'name': 'Documents'|trans, 'route': 'app_doc' },
|
||||
{ 'name': 'Dons'|trans, 'route': 'app_dons' },
|
||||
{ 'name': 'Nous rejoindre'|trans, 'route': 'app_recruit' },
|
||||
{ 'name': 'Contact'|trans, 'route': 'app_contact' }
|
||||
] %}
|
||||
<header class="bg-white shadow-md sticky top-0 z-40">
|
||||
@@ -420,7 +433,7 @@
|
||||
</div>
|
||||
|
||||
{# 2. Réseaux Sociaux #}
|
||||
<div style="height: 210px">gestion commercial
|
||||
<div style="height: 210px">
|
||||
<h3 class="text-lg font-semibold mb-3 text-gray-900">{{ 'footer_follow_us_title'|trans }}</h3>
|
||||
<div class="flex space-x-4">
|
||||
<a href="https://www.facebook.com/assocationecosplay" target="_blank" rel="noopener noreferrer"
|
||||
|
||||
235
templates/join.twig
Normal file
235
templates/join.twig
Normal file
@@ -0,0 +1,235 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}{{'joint_page.title'|trans}}{% endblock %}
|
||||
{% block meta_description %}{{'joint_page.description'|trans}}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_recruit') }}" />{% endblock %}
|
||||
|
||||
{% block breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@type": "ListItem", "position": 1, "name": "{{ 'breadcrumb.home'|trans }}", "item": "{{ app.request.schemeAndHttpHost }}" },
|
||||
{ "@type": "ListItem", "position": 2, "name": "{{ 'breadcrumb.joint'|trans }}", "item": "{{ app.request.schemeAndHttpHost }}{{ path('app_recruit') }}" }
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
"mainEntity": [{
|
||||
"@type": "Question",
|
||||
"name": "{{ 'faq.validation.question'|trans }}",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "{{ 'faq.validation.answer'|trans }}"
|
||||
}
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="bg-white text-gray-900 font-sans leading-normal">
|
||||
|
||||
{# --- HERO SECTION --- #}
|
||||
<section class="relative py-24 bg-[#1A1A1A] text-white overflow-hidden text-center border-b-8 border-[#FFC107]">
|
||||
<div class="absolute inset-0 opacity-10" style="background-image: radial-gradient(circle, #ffffff 1px, transparent 1px); background-size: 40px 40px;"></div>
|
||||
<div class="container mx-auto px-6 relative z-10">
|
||||
<h1 class="text-5xl md:text-8xl font-black mb-6 tracking-tighter uppercase italic">
|
||||
<span class="text-white">{{ 'hero.join.text'|trans }}</span> <span class="text-[#FFC107]">{{ 'hero.brand.name'|trans }}</span>
|
||||
</h1>
|
||||
<div class="inline-block bg-[#E63946] text-white px-10 py-6 rounded-sm shadow-[8px_8px_0px_0px_rgba(255,193,7,1)] transform -rotate-1 border-2 border-black">
|
||||
<span class="text-sm font-black uppercase tracking-widest block mb-1">{{ 'hero.fee.label'|trans }}</span>
|
||||
<div class="text-5xl font-black">{{ 'hero.fee.amount'|trans }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# --- SECTION PROCESSUS D'ADHÉSION --- #}
|
||||
<section class="py-16 bg-gray-50 border-b-4 border-black">
|
||||
<div class="container mx-auto px-6 max-w-5xl">
|
||||
<h2 class="text-4xl font-black uppercase italic mb-10 tracking-tighter text-center">{{ 'process.title'|trans }}</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-8 items-stretch">
|
||||
<div class="bg-white border-4 border-black p-8 shadow-[10px_10px_0px_0px_rgba(26,26,26,1)]">
|
||||
<div class="text-[#E63946] text-4xl font-black mb-4">{{ 'process.unanimous.percent'|trans }}</div>
|
||||
<h3 class="text-xl font-black uppercase mb-4">{{ 'process.unanimous.title'|trans }}</h3>
|
||||
<p class="font-bold text-gray-700 leading-relaxed">
|
||||
{{ 'process.unanimous.description'|trans|raw }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[#1A1A1A] border-4 border-[#FFC107] p-8 text-white shadow-[10px_10px_0px_0px_rgba(255,193,7,1)]">
|
||||
<div class="text-[#FFC107] text-4xl font-black mb-4 text-center">{{ 'process.feedback.icon'|trans }}</div>
|
||||
<h3 class="text-xl font-black uppercase mb-4 text-[#FFC107]">{{ 'process.feedback.title'|trans }}</h3>
|
||||
<p class="font-medium text-gray-300 leading-relaxed text-center">
|
||||
{{ 'process.feedback.description'|trans|raw }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# --- GOUVERNANCE PARTAGÉE --- #}
|
||||
<section class="py-16 bg-[#FFC107] border-b-8 border-black">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="bg-white border-4 border-black p-8 shadow-[10px_10px_0px_0px_rgba(0,0,0,1)]">
|
||||
<h2 class="text-3xl md:text-5xl font-black uppercase italic mb-8 tracking-tighter text-center">{{ 'governance.title'|trans }}</h2>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{% set steps = {
|
||||
'💡': 'governance.step.propose'|trans,
|
||||
'👂': 'governance.step.listen'|trans,
|
||||
'🗳️': 'governance.step.vote'|trans,
|
||||
'🚀': 'governance.step.apply'|trans
|
||||
} %}
|
||||
{% for icon, title in steps %}
|
||||
<div class="p-4 border-2 border-black bg-gray-50 text-center transform hover:scale-105 transition">
|
||||
<div class="text-3xl mb-2">{{ icon }}</div>
|
||||
<h4 class="font-black uppercase text-sm">{{ title }}</h4>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="mt-8 text-center font-black uppercase text-gray-800 tracking-tighter">
|
||||
{{ 'governance.footer'|trans }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# --- SERVICES & HANDICAP --- #}
|
||||
<section class="py-20">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="grid lg:grid-cols-2 gap-12">
|
||||
{# Portfolio #}
|
||||
<div class="flex gap-6 items-start">
|
||||
<div class="bg-black text-[#FFC107] p-4 font-black text-2xl border-2 border-black">01</div>
|
||||
<div>
|
||||
<h3 class="text-2xl font-black uppercase italic">{{ 'services.portfolio.title'|trans }}</h3>
|
||||
<p class="text-gray-600 font-bold">{{ 'services.portfolio.description'|trans }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{# Handicap #}
|
||||
<div class="flex gap-6 items-start p-6 bg-gray-100 border-l-8 border-[#E63946]">
|
||||
<div class="text-5xl">♿</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black uppercase">{{ 'services.inclusion.title'|trans }}</h3>
|
||||
<p class="text-gray-600 italic">{{ 'services.inclusion.description'|trans }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# --- SAFE SPACE --- #}
|
||||
<section class="py-12 bg-[#1A1A1A] text-white text-center">
|
||||
<h2 class="text-2xl font-black uppercase italic text-[#FFC107]">{{ 'safespace.title'|trans }}</h2>
|
||||
<p class="mt-4 font-bold tracking-widest text-sm">{{ 'safespace.subtitle'|trans }}</p>
|
||||
</section>
|
||||
|
||||
{# --- FORMULAIRE --- #}
|
||||
<section class="py-24 bg-gray-50" style="display:none;">
|
||||
<div class="container mx-auto px-6 max-w-4xl">
|
||||
<div class="bg-white border-8 border-black p-8 md:p-12 shadow-[20px_20px_0px_0px_rgba(26,26,26,1)]">
|
||||
<h2 class="text-4xl md:text-6xl font-black uppercase italic mb-10 tracking-tighter text-center decoration-[#FFC107] underline decoration-8">
|
||||
{{ 'form.header.title'|trans }}
|
||||
</h2>
|
||||
|
||||
{{ form_start(form, {'attr': {'class': 'space-y-8','data-turbo':false}}) }}
|
||||
|
||||
{# IDENTITÉ #}
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.name, 'form.label.name'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.name, {'attr': {'class': 'border-4 border-black p-3 focus:bg-yellow-50 outline-none transition'}}) }}
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.surname, 'form.label.surname'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.surname, {'attr': {'class': 'border-4 border-black p-3 focus:bg-yellow-50 outline-none transition'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# CONTACT #}
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.email, 'form.label.email'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.email, {'attr': {'class': 'border-4 border-black p-3 focus:bg-yellow-50 outline-none transition'}}) }}
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.phone, 'form.label.phone'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.phone, {'attr': {'class': 'border-4 border-black p-3 focus:bg-yellow-50 outline-none transition'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# INFOS PERSO #}
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.dateBirth, 'form.label.birthdate'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.dateBirth, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.sexe, 'form.label.gender'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.sexe, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.pronom, 'form.label.pronouns'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.pronom, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ADRESSE #}
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.address, 'form.label.address'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.address, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.zipCode, 'form.label.zipcode'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.zipCode, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.city, 'form.label.city'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.city, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# RÉSEAUX SOCIAUX #}
|
||||
<div class="p-6 bg-gray-100 border-4 border-black border-dashed">
|
||||
<h3 class="font-black uppercase italic mb-4 text-[#E63946]">{{ 'form.section.social'|trans }}</h3>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
{{ form_row(form.discordAccount, {'label': 'form.label.discord'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full'}}) }}
|
||||
{{ form_row(form.instaLink, {'label': 'form.label.insta'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full'}}) }}
|
||||
{{ form_row(form.tiktokLink, {'label': 'form.label.tiktok'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full'}}) }}
|
||||
{{ form_row(form.facebookLink, {'label': 'form.label.facebook'|trans, 'attr': {'class': 'border-2 border-black p-2 w-full'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# RÔLE & MOTIVATION #}
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.who, 'form.label.who'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.who, {'attr': {'class': 'border-4 border-black p-3 min-h-[100px]'}}) }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
{{ form_label(form.role, 'form.label.role'|trans, {'label_attr': {'class': 'font-black uppercase text-sm mb-2'}}) }}
|
||||
{{ form_widget(form.role, {'attr': {'class': 'border-4 border-black p-3'}}) }}
|
||||
</div>
|
||||
|
||||
{# BOUTON ENVOI #}
|
||||
<div class="pt-10 text-center">
|
||||
<button type="submit" class="inline-block px-16 py-8 bg-[#FFC107] text-black font-black text-2xl rounded-sm border-4 border-black shadow-[10px_10px_0px_0px_rgba(26,26,26,1)] hover:bg-[#E63946] hover:text-white transition-all transform hover:-translate-y-2 uppercase cursor-pointer">
|
||||
{{ 'form.button.submit'|trans }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
149
templates/pages/onboarding.twig
Normal file
149
templates/pages/onboarding.twig
Normal file
@@ -0,0 +1,149 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}{{'page.onboarding.title'|trans}}{% endblock %}
|
||||
{% block meta_description %}{{'page.onboarding.description'|trans}}{% endblock %}
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_pages_onboaring',{type:app.request.get('abo','M1')}) }}" />{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{{ form_start(form) }}
|
||||
<!-- Conteneur principal de la carte -->
|
||||
<div class="mt-2 mb-2 w-full max-w-4xl mx-auto mt-2 bg-white shadow-xl rounded-2xl p-6 md:p-10 border border-gray-100">
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-8 text-center">{{ 'onboarding.form.title'|trans }}</h1>
|
||||
|
||||
<!-- Contenu des Étapes (Formulaire) - Tous affichés en colonnes -->
|
||||
<div class="space-y-6">
|
||||
|
||||
<!-- Étape 1 Contenu -->
|
||||
<div class="form-section">
|
||||
<div class="p-6 bg-indigo-50 rounded-xl border border-indigo-200 shadow-sm">
|
||||
<h2 class="text-2xl font-bold text-indigo-700 mb-4 border-b border-indigo-300 pb-2 flex items-center">
|
||||
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
|
||||
{{ 'onboarding.form.section1.title'|trans }}
|
||||
</h2>
|
||||
<p class="text-indigo-600 mb-6">{{ 'onboarding.form.section1.description'|trans }}</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 form-input-group">
|
||||
<!-- Nom -->
|
||||
<div>
|
||||
{{ form_row(form.name) }}
|
||||
</div>
|
||||
<!-- Prénom -->
|
||||
<div>
|
||||
{{ form_row(form.surname) }}
|
||||
</div>
|
||||
<!-- Email -->
|
||||
<div class="md:col-span-1">
|
||||
{{ form_row(form.email) }}
|
||||
</div>
|
||||
<!-- Date de naissance -->
|
||||
<div class="md:col-span-1">
|
||||
{{ form_row(form.birdth) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Étape 2 Contenu -->
|
||||
<div class="p-6 bg-yellow-50 rounded-xl border border-yellow-200 shadow-sm">
|
||||
<h2 class="text-2xl font-bold text-yellow-700 mb-4 border-b border-yellow-300 pb-2 flex items-center">
|
||||
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.639a2 2 0 01-1.789-2.894l3.5-7z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 9H2.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 014.737 3h4.636a2 2 0 011.789 2.894l-3.5 7z"></path></svg>
|
||||
{{ 'onboarding.form.section2.title'|trans }}
|
||||
</h2>
|
||||
<p class="text-yellow-600 mb-6">{{ 'onboarding.form.section2.description'|trans }}</p>
|
||||
<!-- Simulation du contenu du formulaire -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 form-input-group">
|
||||
<div class="md:col-span-2">
|
||||
{{ form_row(form.nameCosplayer) }}
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
{{ form_row(form.description)}}
|
||||
</div>
|
||||
{{ form_row(form.epage) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Étape 3 Contenu -->
|
||||
<div class="p-6 bg-green-50 rounded-xl border border-green-200 shadow-sm">
|
||||
<h2 class="text-2xl font-bold text-green-700 mb-4 border-b border-green-300 pb-2 flex items-center">
|
||||
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.886l2.273 2.273.744-.744-2.273-2.273z"></path></svg>
|
||||
{{ 'onboarding.form.section3.title'|trans }}
|
||||
</h2>
|
||||
<p class="text-green-600 mb-6">{{ 'onboarding.form.section3.description'|trans }}</p>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-2 gap-6 form-input-group">
|
||||
<div>
|
||||
{{ form_row(form.linkFacebook) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_row(form.linkInstagram) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_row(form.linkTiktok) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_row(form.linkX) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Étape 4 Contenu -->
|
||||
<div class="p-6 bg-blue-50 rounded-xl border border-blue-200 shadow-sm">
|
||||
<h2 class="text-2xl font-bold text-blue-700 mb-4 border-b border-blue-200 pb-2 flex items-center">
|
||||
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.5 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.5-3-9s1.343-9 3-9m-9 9h.01"></path></svg>
|
||||
{{ 'onboarding.form.section4.title'|trans }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-6 form-input-group">
|
||||
|
||||
<!-- Checkbox for Custom Domain (form.useDomain) -->
|
||||
<div class="mt-4 flex items-center">
|
||||
{{ form_row(form.useDomain) }}
|
||||
</div>
|
||||
|
||||
<!-- Custom Domain Input (form.domain) - Hidden by default -->
|
||||
<div id="custom-domain-group" class="hidden transition-all duration-300 pt-4 border-t border-blue-200 mt-4">
|
||||
{{ form_row(form.domain) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bouton unique de soumission -->
|
||||
<div class="mt-8 flex justify-center">
|
||||
<button class="px-8 py-3 text-lg font-semibold rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 transition duration-150 shadow-lg shadow-indigo-200">
|
||||
{{ 'onboarding.form.submit_button'|trans }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
|
||||
<script>
|
||||
document.addEventListener('turbo:load', () => {
|
||||
const useDomainCheckbox = document.getElementById('e_page_onboard_useDomain');
|
||||
const customDomainGroup = document.getElementById('custom-domain-group');
|
||||
const customDomainInput = document.getElementById('e_page_onboard_domain');
|
||||
|
||||
function toggleDomainFields() {
|
||||
if (useDomainCheckbox.checked) {
|
||||
// Show custom domain field
|
||||
customDomainGroup.classList.remove('hidden');
|
||||
customDomainInput.setAttribute('required', 'required');
|
||||
|
||||
} else {
|
||||
// Hide custom domain field
|
||||
customDomainGroup.classList.add('hidden');
|
||||
customDomainInput.removeAttribute('required');
|
||||
customDomainInput.value = ''; // Clear custom domain value
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Initial state setup (in case of browser refresh retaining checkbox state)
|
||||
toggleDomainFields();
|
||||
|
||||
// Event listener for the checkbox
|
||||
useDomainCheckbox.addEventListener('change', toggleDomainFields);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -285,7 +285,7 @@
|
||||
</div>
|
||||
|
||||
{# BLOC DE TARIFICATION EPage (CARTE UNIQUE AVEC SÉLECTEUR) - CENTRÉ ET RESPONSIVE #}
|
||||
<div class="mt-16 text-center" style="display:none;">
|
||||
<div class="mt-16 text-center" style="display: none">
|
||||
<h3 class="text-4xl font-extrabold text-gray-800 mb-8">{{ 'page.presentation.pricing.choose_period'|trans }}</h3>
|
||||
|
||||
<div class="max-w-xl mx-auto p-6 sm:p-8 bg-white border-4 border-red-500 rounded-3xl shadow-2xl">
|
||||
@@ -350,7 +350,7 @@
|
||||
<p id="price-per-month" class="text-lg sm:text-xl text-gray-500 mb-8"></p>
|
||||
|
||||
{# BOUTON D'ACTION #}
|
||||
<button class="w-full px-8 py-4 bg-gradient-to-r from-yellow-500 to-red-600 text-white font-extrabold text-xl rounded-xl transition duration-300 transform hover:scale-[1.03] shadow-lg shadow-red-300/50">
|
||||
<button onclick="redirectToCheckout()" class="w-full px-8 py-4 bg-gradient-to-r from-yellow-500 to-red-600 text-white font-extrabold text-xl rounded-xl transition duration-300 transform hover:scale-[1.03] shadow-lg shadow-red-300/50">
|
||||
{{ 'page.presentation.pricing.cta_button'|trans }}
|
||||
</button>
|
||||
|
||||
@@ -446,6 +446,29 @@
|
||||
updatePrice('1M');
|
||||
}
|
||||
|
||||
function redirectToCheckout() {
|
||||
const selectedDurationInput = document.querySelector('input[name="duration"]:checked');
|
||||
|
||||
if (selectedDurationInput) {
|
||||
const selectedDuration = selectedDurationInput.value;
|
||||
|
||||
// --- AJOUT DE LA VALIDATION DE DURÉE ---
|
||||
if (VALID_DURATIONS.includes(selectedDuration)) {
|
||||
// Redirection avec le paramètre 'abo' valide
|
||||
window.location.href = `/pages/onboaring?abo=${selectedDuration}`;
|
||||
} else {
|
||||
// Fallback si la durée sélectionnée n'est pas dans la liste des valides
|
||||
console.error("Durée d'abonnement sélectionnée non valide:", selectedDuration);
|
||||
// On peut rediriger vers la page par défaut si l'on ne veut pas bloquer l'utilisateur
|
||||
window.location.href = `/pages/onboaring?abo=1M`;
|
||||
}
|
||||
} else {
|
||||
// Fallback si rien n'est sélectionné (redirection par défaut vers 1M ou page de découverte)
|
||||
console.error("Aucune durée d'abonnement sélectionnée. Redirection par défaut.");
|
||||
window.location.href = `/pages/onboaring?abo=1M`;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window.Turbo !== 'undefined') {
|
||||
document.addEventListener('turbo:load', initializePrice);
|
||||
} else {
|
||||
|
||||
@@ -777,3 +777,117 @@ page:
|
||||
call_to_action: '准备好加入我们平台上已有的 0 位角色扮演者了吗?'
|
||||
|
||||
breadcrumb: '您的角色扮演者页面'
|
||||
# translations/messages.zh.yaml
|
||||
|
||||
# SEO & Meta
|
||||
joint_page:
|
||||
title: "加入 E-Cosplay - 成为成员"
|
||||
description: "加入 E-Cosplay 协会。这是一个选拔性、包容性且民主的空间,旨在提升您的 Cosplay 才能。"
|
||||
|
||||
breadcrumb.joint: "加入我们"
|
||||
|
||||
# FAQ (JSON-LD)
|
||||
faq:
|
||||
validation:
|
||||
question: "我的入会申请如何审核?"
|
||||
answer: "每一份申请都将由委员会讨论并投票。必须获得 100% 全体一致通过。如被拒绝,我们将通过电子邮件向您发送说明理由的回复。"
|
||||
|
||||
# Hero Section
|
||||
hero:
|
||||
join:
|
||||
text: "加入"
|
||||
brand:
|
||||
name: "E-Cosplay"
|
||||
fee:
|
||||
label: "年度会费"
|
||||
amount: "15,00€"
|
||||
|
||||
# 招募流程 (Membership Process)
|
||||
process:
|
||||
title: "选拔性且透明的招募"
|
||||
unanimous:
|
||||
percent: "100%"
|
||||
title: "全体一致投票"
|
||||
description: "每一份入会申请都由委员会成员讨论并投票。为了保证团队的凝聚力,<strong>必须获得委员会全体成员的同意</strong>才能批准入会。"
|
||||
feedback:
|
||||
icon: "✉️"
|
||||
title: "保证回复"
|
||||
description: "我们尊重每一位申请人。如果您的申请被拒绝,您将收到一份明确告知<strong>拒绝理由</strong>的回复。"
|
||||
|
||||
# 治理 (Governance)
|
||||
governance:
|
||||
title: "协会的生活由你主导"
|
||||
step:
|
||||
propose: "提议"
|
||||
listen: "聆听"
|
||||
vote: "投票"
|
||||
apply: "执行"
|
||||
footer: "每位成员都可以随时提出新的创意或修改方案!"
|
||||
|
||||
# 服务与残障支持 (Services & Disability)
|
||||
services:
|
||||
portfolio:
|
||||
title: "E-Page 个人主页"
|
||||
description: "包含:为您展示照片、链接和社交媒体的专业展示窗口。"
|
||||
inclusion:
|
||||
title: "无障碍支持"
|
||||
description: "我们的主席 Shoko 致力于确保协会内没有任何人因残障而受到限制。"
|
||||
|
||||
# 安全空间 (Safe Space)
|
||||
safespace:
|
||||
title: "🏳️🌈 E-Cosplay 安全空间"
|
||||
subtitle: "尊重身份 • 尊重代词 • 包容彼此"
|
||||
|
||||
# 申请表单 (Application Form)
|
||||
form:
|
||||
choices:
|
||||
gender:
|
||||
not_specified: "未指定"
|
||||
asexual: "无性恋"
|
||||
bisexual: "双性恋"
|
||||
demisexual: "半性恋"
|
||||
gay: "男同性恋"
|
||||
heterosexual: "异性恋"
|
||||
lesbian: "女同性恋"
|
||||
pansexual: "泛性恋"
|
||||
queer: "酷儿"
|
||||
questioning: "探索中"
|
||||
other: "其他"
|
||||
pronouns:
|
||||
il: "他 (He)"
|
||||
elle: "她 (She)"
|
||||
iel: "他们 (They)"
|
||||
autre: "其他 / 自定义"
|
||||
role:
|
||||
cosplay: "Cosplayer (角色扮演者)"
|
||||
helper: "Helper (后勤协助)"
|
||||
photographer: "摄影师"
|
||||
other: "其他"
|
||||
header:
|
||||
title: "申请表"
|
||||
label:
|
||||
name: "姓"
|
||||
surname: "名"
|
||||
email: "电子邮件"
|
||||
phone: "联系电话"
|
||||
birthdate: "出生日期"
|
||||
gender: "性别"
|
||||
pronouns: "首选代词"
|
||||
address: "邮寄地址"
|
||||
zipcode: "邮政编码"
|
||||
city: "城市"
|
||||
discord: "Discord 账号"
|
||||
insta: "Instagram 链接"
|
||||
tiktok: "TikTok 链接"
|
||||
facebook: "Facebook 链接"
|
||||
who: "你是谁?(简单自我介绍)"
|
||||
role: "你希望担任什么角色?"
|
||||
section:
|
||||
social: "社交媒体与作品集"
|
||||
button:
|
||||
submit: "提交申请"
|
||||
|
||||
# 反馈信息
|
||||
form_feedback:
|
||||
success: "您的申请已成功提交!委员会将尽快进行审核。"
|
||||
error: "发生错误,请检查您的信息。"
|
||||
|
||||
@@ -845,3 +845,118 @@ page:
|
||||
call_to_action: 'Ready to join our 0 cosplayers already present on our platform?'
|
||||
|
||||
breadcrumb: 'Your cosplayer page'
|
||||
|
||||
# translations/messages.en.yaml
|
||||
|
||||
# SEO & Meta
|
||||
joint_page:
|
||||
title: "Join E-Cosplay - Become a Member"
|
||||
description: "Join the E-Cosplay association. A selective, inclusive, and democratic space to boost your cosplay talent."
|
||||
|
||||
breadcrumb.joint: "Join us"
|
||||
|
||||
# FAQ (JSON-LD)
|
||||
faq:
|
||||
validation:
|
||||
question: "How is my membership validated?"
|
||||
answer: "Every application is discussed and voted on by the board. Unanimous approval (100%) is required. In case of refusal, a reasoned response will be sent to you by email."
|
||||
|
||||
# Hero Section
|
||||
hero:
|
||||
join:
|
||||
text: "Join"
|
||||
brand:
|
||||
name: "E-Cosplay"
|
||||
fee:
|
||||
label: "Annual Membership Fee"
|
||||
amount: "€15.00"
|
||||
|
||||
# Membership Process
|
||||
process:
|
||||
title: "A selective & transparent recruitment"
|
||||
unanimous:
|
||||
percent: "100%"
|
||||
title: "Unanimous Vote"
|
||||
description: "Each membership request is discussed and voted on by the board members. To ensure team cohesion, <strong>full board approval is required</strong> to validate an entry."
|
||||
feedback:
|
||||
icon: "✉️"
|
||||
title: "Guaranteed Response"
|
||||
description: "We respect every candidate. If your application is declined, you will receive a clear response stating the <strong>reason for refusal</strong>."
|
||||
|
||||
# Governance
|
||||
governance:
|
||||
title: "The life of the association belongs to you"
|
||||
step:
|
||||
propose: "Propose"
|
||||
listen: "Listen"
|
||||
vote: "Vote"
|
||||
apply: "Apply"
|
||||
footer: "Every member can propose a new feature or change at any time!"
|
||||
|
||||
# Services & Disability
|
||||
services:
|
||||
portfolio:
|
||||
title: "E-Page Portfolio"
|
||||
description: "Included: Your professional showcase for your photos, links, and social media."
|
||||
inclusion:
|
||||
title: "Total Accessibility"
|
||||
description: "Shoko, our President, ensures that no one is held back by disability within the association."
|
||||
|
||||
# Safe Space
|
||||
safespace:
|
||||
title: "🏳️🌈 E-Cosplay Safe Space"
|
||||
subtitle: "RESPECT FOR IDENTITIES • RESPECT FOR PRONOUNS • INCLUSION"
|
||||
|
||||
# Application Form
|
||||
form:
|
||||
choices:
|
||||
gender:
|
||||
not_specified: "Not specified"
|
||||
asexual: "Asexual"
|
||||
bisexual: "Bisexual"
|
||||
demisexual: "Demisexual"
|
||||
gay: "Gay"
|
||||
heterosexual: "Heterosexual"
|
||||
lesbian: "Lesbian"
|
||||
pansexual: "Pansexual"
|
||||
queer: "Queer"
|
||||
questioning: "Questioning"
|
||||
other: "Other"
|
||||
pronouns:
|
||||
il: "He/Him"
|
||||
elle: "She/Her"
|
||||
iel: "They/Them"
|
||||
autre: "Other / Custom"
|
||||
role:
|
||||
cosplay: "Cosplayer"
|
||||
helper: "Helper (Staff)"
|
||||
photographer: "Photographer"
|
||||
other: "Other"
|
||||
header:
|
||||
title: "Application"
|
||||
label:
|
||||
name: "Last Name"
|
||||
surname: "First Name"
|
||||
email: "Email"
|
||||
phone: "Phone Number"
|
||||
birthdate: "Date of Birth"
|
||||
gender: "Gender"
|
||||
pronouns: "Pronouns"
|
||||
address: "Mailing Address"
|
||||
zipcode: "Zip Code"
|
||||
city: "City"
|
||||
discord: "Discord Account"
|
||||
insta: "Instagram Link"
|
||||
tiktok: "TikTok Link"
|
||||
facebook: "Facebook Link"
|
||||
who: "Who are you? (Quick introduction)"
|
||||
role: "What role would you like to have?"
|
||||
section:
|
||||
social: "Social Media & Portfolio"
|
||||
button:
|
||||
submit: "Submit my Application"
|
||||
|
||||
# Feedback Messages
|
||||
form_feedback:
|
||||
success: "Your application has been successfully submitted! The board will review it shortly."
|
||||
error: "An error occurred. Please check your information."
|
||||
|
||||
@@ -696,6 +696,9 @@ events.details.back_to_list: Retour à la liste des événements
|
||||
|
||||
|
||||
page:
|
||||
onboarding:
|
||||
title: "Formulaire demande de EPage"
|
||||
description: "Formulaire demande de EPage"
|
||||
presentation:
|
||||
siteconseil_partner:
|
||||
discover_button: Découvrir SITECONSEIL
|
||||
@@ -793,11 +796,10 @@ page:
|
||||
breadcrumb: 'Votre page cosplayer(euse)'
|
||||
|
||||
epage_cosplay: 'EPAGE - Cosplayer(euse)'
|
||||
hero:
|
||||
heading: "L'Innovation au Service de Votre Succès"
|
||||
subheading: "Découvrez comment notre expertise peut transformer vos défis en opportunités de croissance durable."
|
||||
cta_main: "Découvrir Nos Solutions"
|
||||
cta_secondary: "Prendre Rendez-vous"
|
||||
hero.heading: "L'Innovation au Service de Votre Succès"
|
||||
hero.subheading: "Découvrez comment notre expertise peut transformer vos défis en opportunités de croissance durable."
|
||||
hero.cta_main: "Découvrir Nos Solutions"
|
||||
hero.cta_secondary: "Prendre Rendez-vous"
|
||||
page.title: "EPAGE - Cosplayer(euse)"
|
||||
creators:
|
||||
# Pluralisation pour le titre
|
||||
@@ -810,3 +812,153 @@ cta_creator:
|
||||
heading: "Êtes-vous cosplayer(euse) ?"
|
||||
subtext: "Vous voulez une page pour présenter vos cosplay et interagir avec vos fans ?"
|
||||
button: "Découvrir Epage pour les Créateurs"
|
||||
|
||||
|
||||
epage_onboard:
|
||||
name: "Nom"
|
||||
surname: "Prénom"
|
||||
email: "Email"
|
||||
birdth: "Date de naissance"
|
||||
nameCosplayer: Pseudo de Cosplayer
|
||||
description: Description de votre activité (max 500 caractères)
|
||||
linkFacebook: Lien Facebook (URL complète)
|
||||
linkInstagram: Lien Instagram (URL complète)
|
||||
linkTiktok: Lien TikTok (URL complète)
|
||||
linkX: Lien X (Twitter) (URL complète)
|
||||
useDomain: Utiliser un nom de domaine personnalisé
|
||||
domain: "Nom de domaine souhaité (ex: e-cosplay.fr)"
|
||||
avatar: "Votre photos de profil"
|
||||
avatar_label: "Votre photos de profils max (100Mo) aux format png,jpg,jpeg,webp"
|
||||
onboarding:
|
||||
form:
|
||||
submit_button: "Soumettre le Formulaire Complet"
|
||||
section4:
|
||||
title: "4. Lien Personnalisé (EPage)"
|
||||
section3:
|
||||
description: "Ajoutez les liens complets vers vos profils principaux (URL complète)."
|
||||
title: "3. Liens de Réseaux Sociaux"
|
||||
title: "Formulaire demande de EPage"
|
||||
section1:
|
||||
title: "1. Informations Personnelles"
|
||||
description: "Entrez vos coordonnées et informations personnelles."
|
||||
section2:
|
||||
title: "2. Profil Cosplay"
|
||||
description: "Détails sur votre activité, votre pseudo."
|
||||
page_presentation:
|
||||
breadcrumb: EPAGE - Cosplayer(euse)
|
||||
Nous rejoindre: 'Nous Rejoindre'
|
||||
# translations/messages.fr.yaml
|
||||
|
||||
# SEO & Meta
|
||||
joint_page:
|
||||
title: "Rejoindre E-Cosplay - Devenir Membre"
|
||||
description: "Rejoignez l'association E-Cosplay. Un espace sélectif, inclusif et démocratique pour propulser votre talent cosplay."
|
||||
|
||||
breadcrumb.joint: "Nous rejoindre"
|
||||
|
||||
# FAQ (JSON-LD)
|
||||
faq:
|
||||
validation:
|
||||
question: "Comment se passe la validation de mon adhésion ?"
|
||||
answer: "Chaque demande est discutée et votée par le bureau. L'unanimité (100%) est requise. En cas de refus, une réponse motivée vous est envoyée par e-mail."
|
||||
|
||||
# Hero Section
|
||||
hero:
|
||||
join:
|
||||
text: "Rejoindre"
|
||||
brand:
|
||||
name: "E-Cosplay"
|
||||
fee:
|
||||
label: "Cotisation Annuelle"
|
||||
amount: "15,00€"
|
||||
|
||||
# Processus d'adhésion
|
||||
process:
|
||||
title: "Un recrutement sélectif & transparent"
|
||||
unanimous:
|
||||
percent: "100%"
|
||||
title: "Vote à l'unanimité"
|
||||
description: "Chaque demande d'adhésion est discutée et votée par les membres du bureau. Pour garantir la cohésion de l'équipe, <strong>l'accord total du bureau est requis</strong> pour valider une entrée."
|
||||
feedback:
|
||||
icon: "✉️"
|
||||
title: "Réponse garantie"
|
||||
description: "Nous respectons chaque candidat. Si votre demande est refusée, vous recevrez une réponse claire indiquant le <strong>motif du refus</strong>."
|
||||
|
||||
# Gouvernance
|
||||
governance:
|
||||
title: "La vie de l'asso t'appartient"
|
||||
step:
|
||||
propose: "Proposer"
|
||||
listen: "Écouter"
|
||||
vote: "Voter"
|
||||
apply: "Appliquer"
|
||||
footer: "Chaque membre peut proposer une nouveauté à tout moment !"
|
||||
|
||||
# Services & Handicap
|
||||
services:
|
||||
portfolio:
|
||||
title: "Portfolio E-Page"
|
||||
description: "Inclus : Votre vitrine pro pour vos photos, vos liens et vos réseaux."
|
||||
inclusion:
|
||||
title: "Accessibilité totale"
|
||||
description: "Shoko, notre présidente, veille à ce que personne ne soit freiné par le handicap au sein de l'association."
|
||||
|
||||
# Safe Space
|
||||
safespace:
|
||||
title: "🏳️🌈 Safe Space E-Cosplay"
|
||||
subtitle: "RESPECT DES IDENTITÉS • RESPECT DES PRONOMS • INCLUSION"
|
||||
|
||||
# Formulaire de candidature
|
||||
form:
|
||||
choices:
|
||||
gender:
|
||||
not_specified: "Non spécifié"
|
||||
asexual: "Asexuel(le)"
|
||||
bisexual: "Bisexuel(le)"
|
||||
demisexual: "Demisexuel(le)"
|
||||
gay: "Gay"
|
||||
heterosexual: "Hétérosexuel(le)"
|
||||
lesbian: "Lesbienne"
|
||||
pansexual: "Pansexuel(le)"
|
||||
queer: "Queer"
|
||||
questioning: "En questionnement"
|
||||
other: "Autre"
|
||||
pronouns:
|
||||
il: "Il"
|
||||
elle: "Elle"
|
||||
iel: "Iel"
|
||||
autre: "Autre / Personnalisé"
|
||||
role:
|
||||
cosplay: "Cosplayer"
|
||||
helper: "Helper (Aide logistique)"
|
||||
photographer: "Photographe"
|
||||
other: "Autre"
|
||||
header:
|
||||
title: "Candidature"
|
||||
label:
|
||||
name: "Nom"
|
||||
surname: "Prénom"
|
||||
email: "Email"
|
||||
phone: "Téléphone"
|
||||
birthdate: "Date de naissance"
|
||||
gender: "Sexe"
|
||||
pronouns: "Pronoms"
|
||||
address: "Adresse postale"
|
||||
zipcode: "Code Postal"
|
||||
city: "Ville"
|
||||
discord: "Compte Discord"
|
||||
insta: "Lien Instagram"
|
||||
tiktok: "Lien TikTok"
|
||||
facebook: "Lien Facebook"
|
||||
who: "Qui êtes-vous ? (Présentation rapide)"
|
||||
role: "Quel rôle souhaitez-vous occuper ?"
|
||||
section:
|
||||
social: "Réseaux & Portfolio"
|
||||
button:
|
||||
submit: "Envoyer ma candidature"
|
||||
|
||||
# Messages de succès / erreur (Optionnel pour vos contrôleurs)
|
||||
form_feedback:
|
||||
success: "Votre candidature a été envoyée avec succès ! Le bureau l'étudiera prochainement."
|
||||
error: "Une erreur est survenue. Veuillez vérifier vos informations."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user