diff --git a/assets/app.js b/assets/app.js index 8725cc5..06118dd 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,10 +1,2 @@ import './bootstrap.js'; -/* - * Welcome to your app's main JavaScript file! - * - * This file will be included onto the page via the importmap() Twig function, - * which should already be in your base.html.twig. - */ import './styles/app.css'; - -console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); diff --git a/assets/bootstrap.js b/assets/bootstrap.js index d4e50c9..b22f20c 100644 --- a/assets/bootstrap.js +++ b/assets/bootstrap.js @@ -1,5 +1,3 @@ import { startStimulusApp } from '@symfony/stimulus-bundle'; const app = startStimulusApp(); -// register any custom, 3rd party controllers here -// app.register('some_controller_name', SomeImportedController); diff --git a/assets/controllers/form_controller.js b/assets/controllers/form_controller.js index 0fc5078..19b48c3 100644 --- a/assets/controllers/form_controller.js +++ b/assets/controllers/form_controller.js @@ -2,32 +2,54 @@ import {Controller} from "@hotwired/stimulus"; export default class extends Controller { - static targets = ['ticket', 'afterShowTicket', 'kid12yo', 'kid6yo', 'meat', 'vegetarian', 'vegan', 'error'] + addEntry(event) { + event.preventDefault(); - connect() { - this.element.addEventListener('submit', (e) => this.submit(e)) + const formClone = this.element.querySelector('form').cloneNode(true); + + formClone.querySelectorAll("select").forEach(input => { + input.value = "1"; + }); + formClone.querySelector('input').value = ''; + + this.element.querySelector('.forms').appendChild(formClone); } - submit(e) { - e.preventDefault(); - const ticketCount = this.getTotalTicketCount(); - const foodCount = this.getTotalFoodCount(); + removeEntry(event) { + event.preventDefault(); - if (foodCount > ticketCount) { - this.errorTarget.textContent = 'Sie können nicht mehr Essen als Tickets bestellen'; + if (document.querySelector('.forms').childElementCount === 1) { + return; } + + event.target.closest('form').remove(); } - getTotalTicketCount() { - return parseInt(this.ticketTarget.value) - + parseInt(this.kid12yoTarget.value) - + parseInt(this.kid6yoTarget.value) - + parseInt(this.afterShowTicketTarget.value); + submit(event) { + event.preventDefault(); + + const forms = document.querySelectorAll('form'); + const formData = this.getFormData(forms); + + fetch('/ticket/submit', { + method: 'POST', + body: JSON.stringify(formData), + headers: { + 'Content-Type': 'application/json' + } + }).then(response => { + if (!response.ok) { + alert('An error occurred'); + } + }); } - getTotalFoodCount() { - return parseInt(this.meatTarget.value) - + parseInt(this.vegetarianTarget.value) - + parseInt(this.veganTarget.value); + getFormData(forms) { + const formData = []; + forms.forEach(form => { + formData.push(Object.fromEntries(new FormData(form).entries())); + }) + + return formData; } } \ No newline at end of file diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js deleted file mode 100644 index e847027..0000000 --- a/assets/controllers/hello_controller.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller } from '@hotwired/stimulus'; - -/* - * This is an example Stimulus controller! - * - * Any element with a data-controller="hello" attribute will cause - * this controller to be executed. The name "hello" comes from the filename: - * hello_controller.js -> "hello" - * - * Delete this file or adapt it for your use! - */ -export default class extends Controller { - connect() { - this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; - } -} diff --git a/assets/icons/delete.svg b/assets/icons/delete.svg new file mode 100644 index 0000000..c90ee5e --- /dev/null +++ b/assets/icons/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/plus.svg b/assets/icons/plus.svg new file mode 100644 index 0000000..0488d68 --- /dev/null +++ b/assets/icons/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/composer.json b/composer.json index 803b6fd..885348b 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,12 @@ "symfony/framework-bundle": "7.2.*", "symfony/mailer": "7.2.*", "symfony/runtime": "7.2.*", + "symfony/serializer": "7.2.*", "symfony/twig-bundle": "7.2.*", + "symfony/ux-icons": "^2.22", "symfony/ux-turbo": "^2.22", + "symfony/ux-twig-component": "^2.22", + "symfony/validator": "7.2.*", "symfony/yaml": "7.2.*", "symfonycasts/tailwind-bundle": "^0.7.1", "twig/extra-bundle": "^2.12|^3.0", diff --git a/composer.lock b/composer.lock index 436be9d..c083e45 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "7bf03fa2d3a15763608c71cc0c5ad00c", + "content-hash": "51f020bf0346145a2eaa666b7a83d36b", "packages": [ { "name": "composer/semver", @@ -4860,6 +4860,104 @@ ], "time": "2024-11-06T11:43:25+00:00" }, + { + "name": "symfony/serializer", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "320f30beb419ce4f96363ada5e225c41f1ef08ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/320f30beb419ce4f96363ada5e225c41f1ef08ab", + "reference": "320f30beb419ce4f96363ada5e225c41f1ef08ab", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "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": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v7.2.3" + }, + "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": "2025-01-29T07:13:55+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.5.1", @@ -5508,6 +5606,95 @@ ], "time": "2024-12-20T13:38:37+00:00" }, + { + "name": "symfony/ux-icons", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-icons.git", + "reference": "3a6fd4293fc200530b09960c41941a71354bc5fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-icons/zipball/3a6fd4293fc200530b09960c41941a71354bc5fc", + "reference": "3a6fd4293fc200530b09960c41941a71354bc5fc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0" + }, + "conflict": { + "symfony/flex": "<1.13", + "symfony/ux-twig-component": "<2.21" + }, + "require-dev": { + "psr/log": "^2|^3", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "6.4|^7.0", + "symfony/phpunit-bridge": "^6.3|^7.0", + "symfony/ux-twig-component": "^2.14", + "zenstruck/console-test": "^1.5" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Icons\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Bond", + "email": "kevinbond@gmail.com" + }, + { + "name": "Simon André", + "email": "smn.andre@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Renders local and remote SVG icons in your Twig templates.", + "homepage": "https://symfony.com", + "keywords": [ + "icons", + "svg", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-icons/tree/v2.22.1" + }, + "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-12-04T11:34:13+00:00" + }, { "name": "symfony/ux-turbo", "version": "v2.22.1", @@ -5606,6 +5793,186 @@ ], "time": "2024-12-05T14:25:02+00:00" }, + { + "name": "symfony/ux-twig-component", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-twig-component.git", + "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "twig/twig": "^3.8" + }, + "conflict": { + "symfony/config": "<5.4.0" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/phpunit-bridge": "^6.0|^7.0", + "symfony/stimulus-bundle": "^2.9.1", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/webpack-encore-bundle": "^1.15" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\TwigComponent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Twig components for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "components", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-twig-component/tree/v2.22.1" + }, + "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-12-07T18:05:50+00:00" + }, + { + "name": "symfony/validator", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "6faf9f671d522b76ce87e46a1d2d7740b4385c6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/6faf9f671d522b76ce87e46a1d2d7740b4385c6f", + "reference": "6faf9f671d522b76ce87e46a1d2d7740b4385c6f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<7.0", + "symfony/expression-language": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/intl": "<6.4", + "symfony/property-info": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/type-info": "^7.1", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/Resources/bin/" + ] + }, + "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 tools to validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v7.2.3" + }, + "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": "2025-01-28T15:51:35+00:00" + }, { "name": "symfony/var-dumper", "version": "v7.2.0", diff --git a/config/bundles.php b/config/bundles.php index 84d1ca1..6bf095b 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -13,4 +13,6 @@ return [ Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], Symfony\UX\Turbo\TurboBundle::class => ['all' => true], + Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], + Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], ]; diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7e1ee1f..a269f69 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -3,7 +3,8 @@ framework: secret: '%env(APP_SECRET)%' # Note that the session will be started ONLY if you read or write from it. - session: true + session: + cookie_samesite: 'lax' #esi: true #fragments: true diff --git a/config/packages/twig_component.yaml b/config/packages/twig_component.yaml new file mode 100644 index 0000000..fd17ac6 --- /dev/null +++ b/config/packages/twig_component.yaml @@ -0,0 +1,5 @@ +twig_component: + anonymous_template_directory: 'components/' + defaults: + # Namespace & directory for components + App\Twig\Components\: 'components/' diff --git a/config/packages/validator.yaml b/config/packages/validator.yaml new file mode 100644 index 0000000..dd47a6a --- /dev/null +++ b/config/packages/validator.yaml @@ -0,0 +1,11 @@ +framework: + validation: + # Enables validator auto-mapping support. + # For instance, basic validation constraints will be inferred from Doctrine's metadata. + #auto_mapping: + # App\Entity\: [] + +when@test: + framework: + validation: + not_compromised_password: false diff --git a/public/test.txt b/public/test.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Controller/TicketController.php b/src/Controller/TicketController.php index c609852..21996c5 100644 --- a/src/Controller/TicketController.php +++ b/src/Controller/TicketController.php @@ -5,20 +5,33 @@ namespace App\Controller; use App\DataObjects\TicketData; use App\Form\TicketForm; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Serializer\SerializerInterface; final class TicketController extends AbstractController { #[Route('/ticket', name: 'app_ticket')] - public function index(Request $request): Response + public function index(Request $request, Filesystem $f): Response { - $data = new TicketData(); - $form = $this->createForm(TicketForm::class, $data)->handleRequest($request); - +$f->dumpFile('test.txt', $request->getContent()); return $this->render('ticket/index.html.twig', [ - 'form' => $form->createView(), + 'form' => $this->createForm(TicketForm::class)->createView(), ]); } + + #[Route(path: '/ticket/submit', name: 'app_submit', methods: Request::METHOD_POST)] + public function submit(Request $request, SerializerInterface $serializer): Response + { + $ticketData = $serializer->deserialize( + $request->getContent(), + TicketData::class.'[]', + 'json', + ['disable_type_enforcement' => true] + ); + + return $this->json([]); + } } diff --git a/src/DataObjects/TicketData.php b/src/DataObjects/TicketData.php index 13386f0..3c420b6 100644 --- a/src/DataObjects/TicketData.php +++ b/src/DataObjects/TicketData.php @@ -2,14 +2,21 @@ namespace App\DataObjects; +use Symfony\Component\Serializer\Attribute\SerializedName; +use Symfony\Component\Validator\Constraints as Assert; + class TicketData { public function __construct( - public string $notes = '', - public int $ticket = 0, - public int $afterShowTicket = 0, - public int $kids6yo = 0, - public int $kids12yo = 0, + #[Assert\Range(min: 1, max: 3)] + #[SerializedName('ticket')] + public int $ticketType = 0, + + #[Assert\Range(min: 1, max: 3)] + #[SerializedName('food')] + public int $foodType = 0, + + public string $note = '' ) { } } \ No newline at end of file diff --git a/src/Form/TicketForm.php b/src/Form/TicketForm.php index 2af1376..3d33704 100644 --- a/src/Form/TicketForm.php +++ b/src/Form/TicketForm.php @@ -3,21 +3,12 @@ namespace App\Form; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\EmailType; -use Symfony\Component\Form\Extension\Core\Type\NumberType; -use Symfony\Component\Form\Extension\Core\Type\TextareaType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; class TicketForm extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { - $builder - ->add('ticket', NumberType::class) - ->add('afterShowTicket', NumberType::class) - ->add('kids6yo', NumberType::class) - ->add('kids12yo', NumberType::class) - ->add('notes', TextareaType::class); + $builder; } } \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 851052b..ede50e2 100644 --- a/symfony.lock +++ b/symfony.lock @@ -165,6 +165,18 @@ "templates/base.html.twig" ] }, + "symfony/ux-icons": { + "version": "2.22", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.17", + "ref": "803a3bbd5893f9584969ab8670290cdfb6a0a5b5" + }, + "files": [ + "assets/icons/symfony.svg" + ] + }, "symfony/ux-turbo": { "version": "2.22", "recipe": { @@ -174,6 +186,30 @@ "ref": "c85ff94da66841d7ff087c19cbcd97a2df744ef9" } }, + "symfony/ux-twig-component": { + "version": "2.22", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "67814b5f9794798b885cec9d3f48631424449a01" + }, + "files": [ + "config/packages/twig_component.yaml" + ] + }, + "symfony/validator": { + "version": "7.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd" + }, + "files": [ + "config/packages/validator.yaml" + ] + }, "symfony/web-profiler-bundle": { "version": "7.2", "recipe": { diff --git a/templates/ticket/_partials/_form.html.twig b/templates/ticket/_partials/_form.html.twig new file mode 100644 index 0000000..1d15a80 --- /dev/null +++ b/templates/ticket/_partials/_form.html.twig @@ -0,0 +1,17 @@ +
diff --git a/templates/ticket/index.html.twig b/templates/ticket/index.html.twig index 7f53ea6..e52f7c4 100644 --- a/templates/ticket/index.html.twig +++ b/templates/ticket/index.html.twig @@ -20,76 +20,22 @@