14 Commits

Author SHA1 Message Date
52c600b32b test 2025-04-25 13:05:47 +02:00
31c039e6dd fix 500 after buying ticket
Reviewed-on: #53
2025-03-18 15:36:23 +00:00
c0302e8dd5 add migration
Reviewed-on: #52
2025-03-18 15:20:33 +00:00
02cbfd9c6b fix 500 when sorting
Reviewed-on: #51
2025-03-18 15:01:03 +00:00
fe9e1197a7 improve logging
Reviewed-on: #49
2025-03-16 20:40:43 +00:00
23b050ef98 change ablauf add ticket notes
Reviewed-on: #48
2025-03-15 13:56:08 +00:00
6e0c32f9d2 add logging
Reviewed-on: #47
2025-03-11 10:04:02 +00:00
6e0168ce52 new ticket add clothing type (#46)
Reviewed-on: #46
2025-03-03 21:00:46 +00:00
24f28c24f2 delete customers (#45)
Reviewed-on: #45
2025-03-03 20:39:21 +00:00
92d4225aa9 fix admin tickets (#44)
Reviewed-on: #44
2025-03-01 21:19:50 +00:00
9d95a68efa add 12-15 ticket (#43)
Reviewed-on: #43
2025-03-01 21:12:28 +00:00
c008ebd540 fix tickets getting cut in order pdf (#42)
Reviewed-on: #42
2025-02-28 22:39:45 +00:00
b98bc363d5 change some dates (#41)
Reviewed-on: #41
2025-02-28 13:59:41 +00:00
d242c9b006 feat(ticket): add email confirmation message to success page (#40)
Co-authored-by: Jan Klattenhoff <jan@kjan.email>
Reviewed-on: #40
2025-02-28 13:41:49 +00:00
20 changed files with 619 additions and 153 deletions

View File

@ -23,3 +23,5 @@ RUN composer install --optimize-autoloader --no-suggest --no-progress
RUN php bin/console tailwind:build RUN php bin/console tailwind:build
RUN php bin/console asset-map:compile RUN php bin/console asset-map:compile
RUN echo "APP_ENV=prod" > .env.local RUN echo "APP_ENV=prod" > .env.local
RUN php bin/console cache:clear --env=prod
RUN php bin/console cache:warmup --env=prod

View File

@ -11,7 +11,7 @@ export default class extends Controller {
} }
hide() { hide() {
if (this.ticketSelectTarget.value === '2') { if (this.ticketSelectTarget.value === '2' || this.ticketSelectTarget.value === '0') {
this.foodSelectTarget.disabled = true; this.foodSelectTarget.disabled = true;
this.prevValue = this.foodSelectTarget.value; this.prevValue = this.foodSelectTarget.value;
this.foodSelectTarget.value = '0'; this.foodSelectTarget.value = '0';

View File

@ -26,6 +26,7 @@
"symfony/form": "7.2.*", "symfony/form": "7.2.*",
"symfony/framework-bundle": "7.2.*", "symfony/framework-bundle": "7.2.*",
"symfony/mailer": "7.2.*", "symfony/mailer": "7.2.*",
"symfony/monolog-bundle": "^3.10",
"symfony/property-access": "7.2.*", "symfony/property-access": "7.2.*",
"symfony/property-info": "7.2.*", "symfony/property-info": "7.2.*",
"symfony/runtime": "7.2.*", "symfony/runtime": "7.2.*",

264
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "8521fd86a606df918d7740b6fee8a401", "content-hash": "9f1eaabffc224672a0ec60f33ea2e1fe",
"packages": [ "packages": [
{ {
"name": "chillerlan/php-qrcode", "name": "chillerlan/php-qrcode",
@ -1927,6 +1927,109 @@
}, },
"time": "2024-03-31T07:05:07+00:00" "time": "2024-03-31T07:05:07+00:00"
}, },
{
"name": "monolog/monolog",
"version": "3.8.1",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
"reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/log": "^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2.0",
"guzzlehttp/guzzle": "^7.4.5",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"php-console/php-console": "^3.1.8",
"phpstan/phpstan": "^2",
"phpstan/phpstan-deprecation-rules": "^2",
"phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^10.5.17 || ^11.0.7",
"predis/predis": "^1.1 || ^2",
"rollbar/rollbar": "^4.0",
"ruflin/elastica": "^7 || ^8",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/3.8.1"
},
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2024-12-05T17:15:07+00:00"
},
{ {
"name": "nucleos/dompdf-bundle", "name": "nucleos/dompdf-bundle",
"version": "4.3.0", "version": "4.3.0",
@ -5129,6 +5232,165 @@
], ],
"time": "2024-12-07T08:50:44+00:00" "time": "2024-12-07T08:50:44+00:00"
}, },
{
"name": "symfony/monolog-bridge",
"version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bridge.git",
"reference": "bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d",
"reference": "bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d",
"shasum": ""
},
"require": {
"monolog/monolog": "^3",
"php": ">=8.2",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3"
},
"conflict": {
"symfony/console": "<6.4",
"symfony/http-foundation": "<6.4",
"symfony/security-core": "<6.4"
},
"require-dev": {
"symfony/console": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/mailer": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/mime": "^6.4|^7.0",
"symfony/security-core": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0"
},
"type": "symfony-bridge",
"autoload": {
"psr-4": {
"Symfony\\Bridge\\Monolog\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides integration for Monolog with various Symfony components",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/monolog-bridge/tree/v7.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-10-14T18:16:08+00:00"
},
{
"name": "symfony/monolog-bundle",
"version": "v3.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bundle.git",
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
"shasum": ""
},
"require": {
"monolog/monolog": "^1.25.1 || ^2.0 || ^3.0",
"php": ">=7.2.5",
"symfony/config": "^5.4 || ^6.0 || ^7.0",
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
"symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/phpunit-bridge": "^6.3 || ^7.0",
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bundle\\MonologBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony MonologBundle",
"homepage": "https://symfony.com",
"keywords": [
"log",
"logging"
],
"support": {
"issues": "https://github.com/symfony/monolog-bundle/issues",
"source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-11-06T17:08:13+00:00"
},
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v7.2.0", "version": "v7.2.0",

View File

@ -17,4 +17,5 @@ return [
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true], EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
]; ];

View File

@ -0,0 +1,56 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
when@dev:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
when@test:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
channels: ["!event"]
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
when@prod:
monolog:
handlers:
main:
type: stream
path: php://stderr
level: error
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: [deprecation]
path: php://stderr
formatter: monolog.formatter.json

View File

@ -0,0 +1,55 @@
<?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 Version20250318151059 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// First make sure customer_id is nullable
$this->addSql('ALTER TABLE payment ALTER COLUMN customer_id DROP NOT NULL');
// Add payment_id to customer
$this->addSql('ALTER TABLE customer ADD payment_id INT DEFAULT NULL');
// Migrate data
$this->addSql('UPDATE customer c SET payment_id = (SELECT p.id FROM payment p WHERE p.customer_id = c.id)');
// Add constraints
$this->addSql('ALTER TABLE customer ADD CONSTRAINT FK_81398E094C3A3BB FOREIGN KEY (payment_id) REFERENCES payment (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE UNIQUE INDEX UNIQ_81398E094C3A3BB ON customer (payment_id)');
// Remove old relationship
$this->addSql('ALTER TABLE payment DROP CONSTRAINT fk_6d28840d9395c3f3');
$this->addSql('DROP INDEX uniq_6d28840d9395c3f3');
$this->addSql('ALTER TABLE payment DROP customer_id');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE payment ADD customer_id INT NOT NULL');
$this->addSql('ALTER TABLE
payment
ADD
CONSTRAINT fk_6d28840d9395c3f3 FOREIGN KEY (customer_id) REFERENCES customer (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE UNIQUE INDEX uniq_6d28840d9395c3f3 ON payment (customer_id)');
$this->addSql('ALTER TABLE customer DROP CONSTRAINT FK_81398E094C3A3BB');
$this->addSql('DROP INDEX UNIQ_81398E094C3A3BB');
$this->addSql('ALTER TABLE customer DROP payment_id');
}
}

View File

@ -40,6 +40,10 @@ class CustomerCrudController extends AbstractCrudController
->setFormTypeOptions(['by_reference' => false]) ->setFormTypeOptions(['by_reference' => false])
->setTemplatePath('admin/customer_tickets.html.twig') ->setTemplatePath('admin/customer_tickets.html.twig')
->hideOnIndex(); ->hideOnIndex();
yield AssociationField::new('payment', 'Zahlungs Status')
->setCrudController(PaymentCrudController::class)
->hideOnForm()
->formatValue(fn(?Payment $payment) => $payment?->isCompleted() ? 'Bezahlt' : 'Offen');
} }
@ -47,7 +51,7 @@ class CustomerCrudController extends AbstractCrudController
{ {
return $actions return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL) ->add(Crud::PAGE_INDEX, Action::DETAIL)
->disable(Action::DELETE) ->setPermission(Action::DELETE, 'ROLE_SUPER_ADMIN')
->setPermission(Action::NEW, 'ROLE_SUPER_ADMIN') ->setPermission(Action::NEW, 'ROLE_SUPER_ADMIN')
->setPermission(Action::EDIT, 'ROLE_SUPER_ADMIN'); ->setPermission(Action::EDIT, 'ROLE_SUPER_ADMIN');
} }

View File

@ -30,10 +30,10 @@ class Customer implements \Stringable
/** /**
* @var Collection<int, Ticket> * @var Collection<int, Ticket>
*/ */
#[ORM\OneToMany(targetEntity: Ticket::class, mappedBy: 'customer', cascade: ['persist'], fetch: 'EAGER', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: Ticket::class, mappedBy: 'customer', cascade: ['persist', 'remove'], fetch: 'EAGER', orphanRemoval: true)]
private Collection $tickets; private Collection $tickets;
#[ORM\OneToOne(mappedBy: 'customer', cascade: ['persist', 'remove'])] #[ORM\OneToOne(inversedBy: 'customer', cascade: ['persist', 'remove'])]
private ?Payment $payment = null; private ?Payment $payment = null;
public function __construct() public function __construct()

View File

@ -20,8 +20,7 @@ class Payment
#[ORM\Column] #[ORM\Column]
private ?bool $completed = null; private ?bool $completed = null;
#[ORM\OneToOne(inversedBy: 'payment', cascade: ['persist', 'remove'], fetch: 'EAGER')] #[ORM\OneToOne(mappedBy: 'payment', cascade: ['persist', 'remove'], fetch: 'EAGER')]
#[ORM\JoinColumn(nullable: false)]
private ?Customer $customer = null; private ?Customer $customer = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]

View File

@ -5,28 +5,40 @@ namespace App\Enum;
class TicketData class TicketData
{ {
public const TICKET_DATA = [ public const TICKET_DATA = [
0 => [
'name' => 'Zeugnisvergabe',
'price' => 0,
],
1 => [ 1 => [
'name' => 'All-Inclusive Ticket', 'name' => 'All-Inclusive Ticket',
'price' => 50, 'price' => 50,
'note' => 'Harter Alkohol nicht inbegriffen',
],
3 => [
'name' => 'Kind (12-15 Jahre)',
'price' => 25,
],
4 => [
'name' => 'Kind (6-12 Jahre)',
'price' => 15,
],
5 => [
'name' => 'Kind (0-6 Jahre)',
'price' => 0,
], ],
2 => [ 2 => [
'name' => 'After-Show Ticket', 'name' => 'After-Show Ticket',
'price' => 20, 'price' => 20,
], 'note' => 'Einlass ab 22 Uhr',
3 => [
'name' => 'Kind (6-12 Jahre)',
'price' => 15,
],
4 => [
'name' => 'Kind (0-6 Jahre)',
'price' => 0,
], ],
]; ];
public const TYPES = [ public const TYPES = [
'Zeugnisvergabe' => 0,
'All-Inclusive Ticket' => 1, 'All-Inclusive Ticket' => 1,
'After-Show Ticket' => 2, 'After-Show Ticket' => 2,
'Kind (6-12 Jahre)' => 3, 'Kind (12-15 Jahre)' => 3,
'Kind (0-6 Jahre)' => 4, 'Kind (6-12 Jahre)' => 4,
'Kind (0-6 Jahre)' => 5,
]; ];
} }

View File

@ -12,6 +12,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\VarDumper\Cloner\Data;
use Twig\Environment; use Twig\Environment;
class TicketEmailService class TicketEmailService
@ -33,14 +34,25 @@ class TicketEmailService
->to(new Address($payment->getCustomer()?->getEmail(), $payment->getCustomer()?->getFirstname() . ' ' . $payment->getCustomer()?->getLastname())) ->to(new Address($payment->getCustomer()?->getEmail(), $payment->getCustomer()?->getFirstname() . ' ' . $payment->getCustomer()?->getLastname()))
->context([ ->context([
'payment' => $payment, 'payment' => $payment,
]) ]);
->addPart(new DataPart($this->generateTicket($payment->getCustomer()?->getTickets()), 'tickets.pdf', 'application/pdf'));
$acc = 0;
foreach ($this->generateTicket($payment->getCustomer()?->getTickets()) as $pdf) {
$mail->attach($pdf, 'tickets-'.++$acc.'.pdf', 'application/pdf');
}
$this->mailer->send($mail); $this->mailer->send($mail);
} }
private function generateTicket(Collection $tickets): string private function generateTicket(Collection $tickets): array
{ {
return (new DompdfWrapper(new DompdfFactory()))->getPdf($this->twig->render('email/pdf.html.twig', ['tickets' => $tickets])); $ticketChunks = array_chunk($tickets->toArray(), 3);
$pdfs = [];
foreach ($ticketChunks as $ticket) {
$pdfs[] = (new DompdfWrapper(new DompdfFactory()))->getPdf($this->twig->render('email/pdf.html.twig', ['tickets' => $ticket]));
}
return $pdfs;
} }
} }

View File

@ -54,12 +54,15 @@ class TicketService
private function saveTicketData(TicketFormData $data, string $sessionId): void private function saveTicketData(TicketFormData $data, string $sessionId): void
{ {
$customer = $this->createEntityFromData($data);
$payment = (new Payment()) $payment = (new Payment())
->setSessionId($sessionId) ->setSessionId($sessionId)
->setCompleted(false) ->setCompleted(false)
->setCustomer($this->createEntityFromData($data))
->setDonation($data->personal->donation); ->setDonation($data->personal->donation);
$customer->setPayment($payment);
$this->em->persist($customer);
$this->em->persist($payment); $this->em->persist($payment);
$this->em->flush(); $this->em->flush();
} }

30
src/Subcriber.php Normal file
View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class Subcriber implements EventSubscriberInterface
{
public function __construct(
private LoggerInterface $logger,
)
{
}
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => 'logException'
];
}
public function logException(ExceptionEvent $event): void
{
$this->logger->error($event->getThrowable()->getMessage());
}
}

View File

@ -151,6 +151,18 @@
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
} }
}, },
"symfony/monolog-bundle": {
"version": "3.10",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.7",
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
},
"files": [
"config/packages/monolog.yaml"
]
},
"symfony/routing": { "symfony/routing": {
"version": "7.2", "version": "7.2",
"recipe": { "recipe": {

View File

@ -15,13 +15,5 @@
</head> </head>
<body data-turbo="true"> <body data-turbo="true">
{% block body %}{% endblock %} {% block body %}{% endblock %}
<footer class="w-full bg-white/90 backdrop-blur-md border-t border-gray-100 py-4">
<div class="container mx-auto px-4 sm:px-6 lg:px-8 flex justify-center items-center space-x-4 text-sm text-gray-500">
<a href="https://www.waldorfschule-bremen-osterholz.de/index.php/impressum" target="_blank" class="hover:text-gray-700 transition-colors">Impressum</a>
<span class="text-gray-300">|</span>
<a href="https://www.waldorfschule-bremen-osterholz.de/index.php/datenschutzerklaerung" target="_blank" class="hover:text-gray-700 transition-colors">Datenschutz</a>
</div>
</footer>
</body> </body>
</html> </html>

View File

@ -3,126 +3,146 @@
{% block title %}Home{% endblock %} {% block title %}Home{% endblock %}
{% block body %} {% block body %}
<div class="min-h-screen relative overflow-hidden" style="background-image: url('{{ asset('images/backgrounds/abiball_bg.jpeg') }}'); background-size: cover; background-position: center; background-attachment: fixed;"> <div class="min-h-screen relative overflow-hidden"
style="background-image: url('{{ asset('images/backgrounds/abiball_bg.jpeg') }}'); background-size: cover; background-position: center; background-attachment: fixed;">
<div class="min-h-screen bg-black/30 backdrop-blur-[2px] relative overflow-hidden"> <div class="min-h-screen bg-black/30 backdrop-blur-[2px] relative overflow-hidden">
<header class="w-full bg-white/90 backdrop-blur-md shadow-lg fixed top-0 z-50 border-b border-gray-100"> <header class="w-full bg-white/90 backdrop-blur-md shadow-lg fixed top-0 z-50 border-b border-gray-100">
<div class="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-3 sm:py-4 flex justify-between items-center"> <div class="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-3 sm:py-4 flex justify-between items-center">
<a href="{{ path('app_home') }}" class="flex items-center space-x-4"> <a href="{{ path('app_home') }}" class="flex items-center space-x-4">
<img src="{{ asset('images/logos/new_logo.png') }}" alt="Waldorfschule Bremen Osterholz" <img src="{{ asset('images/logos/new_logo.png') }}" alt="Waldorfschule Bremen Osterholz"
class="w-32 sm:w-36 md:w-40 h-auto hover:opacity-90 transition-opacity"/> class="w-32 sm:w-36 md:w-40 h-auto hover:opacity-90 transition-opacity"/>
<img src="{{ asset('images/logos/second_logo.png') }}" alt="Secondary Logo" <img src="{{ asset('images/logos/second_logo.png') }}" alt="Secondary Logo"
class="w-32 sm:w-36 md:w-40 h-auto hover:opacity-90 transition-opacity sm:block hidden"/> class="w-32 sm:w-36 md:w-40 h-auto hover:opacity-90 transition-opacity sm:block hidden"/>
</a> </a>
<a href="{{ path('app_contact') }}" <a href="{{ path('app_contact') }}"
class="bg-gradient-to-r from-red-500 to-orange-500 hover:from-red-600 hover:to-orange-600 text-white px-4 sm:px-6 md:px-8 py-2 sm:py-2.5 rounded-full text-xs sm:text-sm font-medium shadow-md hover:shadow-lg transition-all duration-300"> class="bg-gradient-to-r from-red-500 to-orange-500 hover:from-red-600 hover:to-orange-600 text-white px-4 sm:px-6 md:px-8 py-2 sm:py-2.5 rounded-full text-xs sm:text-sm font-medium shadow-md hover:shadow-lg transition-all duration-300">
Kontaktaufnahme Kontaktaufnahme
</a>
</div>
</header>
<main class="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pt-24 sm:pt-28 md:pt-32 pb-16 sm:pb-20 flex flex-col items-center justify-center relative z-10">
<h1 class="text-3xl sm:text-4xl md:text-5xl font-bold text-white text-center mb-6 sm:mb-8 tracking-tight leading-tight">
Willkommen zum <span class="bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Abiball 2025</span>
</h1>
<div class="w-full max-w-6xl">
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-6 sm:p-8 md:p-10 mb-6 sm:mb-8 transform transition-all duration-300 border border-gray-100">
<h1 class="text-3xl">Willkommen zum Abiball 2025! 🎉🎓</h1>
<p class="text-base sm:text-lg text-gray-700 leading-relaxed whitespace-pre-line text-wrap">
Nach einer unvergesslichen Schulzeit feiern wir gemeinsam unseren Abschluss.
Und ihr seid herzlich eingeladen!
Lasst uns gemeinsam diesen besonderen Abend genießen! 🥂✨
Euer Abijahrgang 2025 🚀
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 w-full gap-3 mb-5">
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-5 mb-3 sm:mb-12 transform transition-all duration-300 border border-gray-100">
<h2 class="text-xl text-center font-semibold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Datum & Uhrzeit</h2>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:calendar" class="w-5 h-5 text-orange-600" />
<span class="text-gray-700">28. Juni 2025</span>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:clock" class="w-5 h-5 text-orange-600" />
<span class="text-gray-700">17:30 - 03:00</span>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:map-pin" class="w-5 h-5 text-orange-600" />
<span class="text-gray-700">Graubündener Str. 4, 28325 Bremen</span>
</div>
</div>
</div>
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-5 mb-3 sm:mb-12 transform transition-all duration-300 border border-gray-100">
<h2 class="text-xl text-center font-semibold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Ablauf</h2>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:ticket" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">17:30-19:30</span>
<span class="ml-2">Einlass & Platznehmen</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:cake" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">19:30-22:00</span>
<span class="ml-2">Dinner & Beisammensein</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:musical-note" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">ab 22:00</span>
<span class="ml-2">Feier & Tanz im Saal</span>
</div>
</div>
</div>
</div>
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-5 mb-3 sm:mb-12 transform transition-all duration-300 border border-gray-100">
<h2 class="text-xl text-center font-semibold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Ticket Preise</h2>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:ticket" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">All-Inclusive Ticket</span>
<span class="ml-2">50€</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:cake" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">After-Show Ticket</span>
<span class="ml-2">20€</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:musical-note" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">Kind (6-12 Jahre)</span>
<span class="ml-2">15€</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:musical-note" class="w-5 h-5 text-orange-600" />
<div class="text-gray-700">
<span class="font-medium">Kind (0-6 Jahre)</span>
<span class="ml-2">0€</span>
</div>
</div>
</div>
</div>
</div>
<div class="text-center space-y-6">
<a href="{{ path('app_ticket') }}"
class="inline-block bg-gradient-to-r from-red-500 to-orange-500 hover:from-red-600 hover:to-orange-600 text-white px-8 sm:px-10 md:px-12 py-4 sm:py-5 rounded-full text-base sm:text-lg font-semibold shadow-xl hover:shadow-2xl transition-all duration-300">
Tickets kaufen
</a> </a>
</div> </div>
</div> </header>
</main>
</div> <main class="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pt-24 sm:pt-28 md:pt-32 pb-16 sm:pb-20 flex flex-col items-center justify-center relative z-10">
<h1 class="text-3xl sm:text-4xl md:text-5xl font-bold text-white text-center mb-6 sm:mb-8 tracking-tight leading-tight">
Willkommen zum <span
class="bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Abiball 2025</span>
</h1>
<div class="w-full max-w-6xl">
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-6 sm:p-8 md:p-10 mb-6 sm:mb-8 transform transition-all duration-300 border border-gray-100">
<h1 class="text-3xl">Willkommen zum Abiball 2025! 🎉🎓</h1>
<p class="text-base sm:text-lg text-gray-700 leading-relaxed whitespace-pre-line text-wrap">
Nach einer unvergesslichen Schulzeit feiern wir gemeinsam unseren Abschluss.
Und ihr seid herzlich eingeladen!
Lasst uns gemeinsam diesen besonderen Abend genießen! 🥂✨
Euer Abijahrgang 2025 🚀
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 w-full gap-3 mb-5">
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-5 mb-3 sm:mb-12 transform transition-all duration-300 border border-gray-100">
<h2 class="text-xl text-center font-semibold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">
Datum & Uhrzeit</h2>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:calendar" class="w-5 h-5 text-orange-600"/>
<span class="text-gray-700">28. Juni 2025</span>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:clock" class="w-5 h-5 text-orange-600"/>
<span class="text-gray-700">17:30 - 03:00</span>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:map-pin" class="w-5 h-5 text-orange-600"/>
<span class="text-gray-700">Graubündener Str. 4, 28325 Bremen</span>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="tabler:shirt" class="w-5 h-5 text-orange-600"/>
<span class="text-gray-700">Abendgarderobe</span>
</div>
</div>
</div>
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-5 mb-3 sm:mb-12 transform transition-all duration-300 border border-gray-100">
<h2 class="text-xl text-center font-semibold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">
Ablauf</h2>
<div class="space-y-6">
<div>
<h3 class="text-lg font-semibold mb-3">Freitag (27. Juni 2025)</h3>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:academic-cap"
class="w-5 h-5 text-orange-600"/>
<div class="text-gray-700">
<span class="font-medium">17:30-19:30</span>
<span class="ml-2">Zeugnisvergabe</span>
</div>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold mb-3">Samstag (28. Juni 2025)</h3>
<div class="space-y-3">
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:ticket" class="w-5 h-5 text-orange-600"/>
<div class="text-gray-700">
<span class="font-medium">17:30-19:30</span>
<span class="ml-2">Einlass & Platznehmen</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:cake" class="w-5 h-5 text-orange-600"/>
<div class="text-gray-700">
<span class="font-medium">19:30-22:00</span>
<span class="ml-2">Dinner & Beisammensein</span>
</div>
</div>
<div class="flex items-center space-x-3">
<twig:ux:icon name="heroicons:musical-note"
class="w-5 h-5 text-orange-600"/>
<div class="text-gray-700">
<span class="font-medium">22:00-03:00</span>
<span class="ml-2">Feier & Tanz im Saal</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-5 mb-3 sm:mb-12 transform transition-all duration-300 border border-gray-100">
<h2 class="text-xl text-center font-semibold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">
Ticket Preise</h2>
<div class="space-y-3">
{% for ticket in constant('App\\Enum\\TicketData::TICKET_DATA') %}
<div class="flex items-center space-x-3 w-full">
<twig:ux:icon name="heroicons:ticket" class="w-5 h-5 text-orange-600"/>
<div class="text-gray-700 w-full flex justify-between">
<span class="font-medium">
{{ ticket['name'] }}
{% if ticket.note is defined %}
<span class="relative group">
<span class="cursor-help text-orange-600">*</span>
<span class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 w-64 px-3 py-2 bg-gray-800 text-white text-sm rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10">
{{ ticket.note }}
</span>
</span>
{% endif %}
</span>
<span class="mr-2">{{ ticket['price'] }}€</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="text-center space-y-6">
<a href="{{ path('app_ticket') }}"
class="inline-block bg-gradient-to-r from-red-500 to-orange-500 hover:from-red-600 hover:to-orange-600 text-white px-8 sm:px-10 md:px-12 py-4 sm:py-5 rounded-full text-base sm:text-lg font-semibold shadow-xl hover:shadow-2xl transition-all duration-300">
Tickets kaufen
</a>
</div>
</div>
</main>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -13,10 +13,9 @@
data-hide-food-target="ticketSelect" data-hide-food-target="ticketSelect"
> >
<option value="" disabled selected>Auswahl</option> <option value="" disabled selected>Auswahl</option>
<option value="1">All-Inclusive Ticket</option> {% for key, ticket in constant('App\\Enum\\TicketData::TICKET_DATA') %}
<option value="2">After-Show Ticket</option> <option value="{{ key }}">{{ ticket['name'] }}</option>
<option value="3">Kinder-Ticket (6-12 Jahre)</option> {% endfor %}
<option value="4">Klein-Kind (0-6 Jahre)</option>
</select> </select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3.5 pointer-events-none"> <div class="absolute inset-y-0 right-0 flex items-center pr-3.5 pointer-events-none">
<twig:ux:icon name="mdi:chevron-down" <twig:ux:icon name="mdi:chevron-down"

View File

@ -42,7 +42,7 @@ Danke
</div> </div>
<div class="flex justify-start space-x-3"> <div class="flex justify-start space-x-3">
<twig:ux:icon name="heroicons:clock" class="w-6 h-6 text-orange-600" /> <twig:ux:icon name="heroicons:clock" class="w-6 h-6 text-orange-600" />
<p class="text-gray-700">17:30 - Open End</p> <p class="text-gray-700">17:30 - 03:00</p>
</div> </div>
<div class="flex justify-start space-x-3"> <div class="flex justify-start space-x-3">
<twig:ux:icon name="heroicons:map-pin" class="w-6 h-6 text-orange-600" /> <twig:ux:icon name="heroicons:map-pin" class="w-6 h-6 text-orange-600" />
@ -80,6 +80,12 @@ Danke
</div> </div>
</div> </div>
<div class="mt-8 bg-white rounded-2xl shadow-lg p-8 border border-gray-100">
<twig:ux:icon name="heroicons:envelope" class="w-8 h-8 text-orange-600 mx-auto mb-4" />
<h3 class="text-xl font-semibold mb-3 text-center">E-Mail Bestätigung</h3>
<p class="text-gray-700 mb-4 text-center">Bitte überprüfe auch deinen Spam-Ordner nach der Bestellbestätigung. Falls du keine E-Mail erhalten hast, kannst du am Eingang einfach deinen vollständigen Namen nennen.</p>
</div>
<div class="mt-8 pt-6 border-t border-gray-200 text-center"> <div class="mt-8 pt-6 border-t border-gray-200 text-center">
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<a href="{{ path('app_calendar') }}" <a href="{{ path('app_calendar') }}"

0
test Normal file
View File