From 5d41b6fef5574368e6fa1a79d36c5448b02ea97b Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 8 Jul 2024 21:23:35 +0200 Subject: [PATCH 01/76] #33: limit orders on first page and paginate --- src/Controller/FoodOrderController.php | 27 +++++++++++++++++--- src/Repository/FoodOrderRepository.php | 23 ++++++++++++----- templates/food_order/index.html.twig | 22 ++++++++++------ tests/Controller/FoodOrderControllerTest.php | 4 +-- 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/Controller/FoodOrderController.php b/src/Controller/FoodOrderController.php index 50e5df4..9fdb77d 100644 --- a/src/Controller/FoodOrderController.php +++ b/src/Controller/FoodOrderController.php @@ -14,11 +14,32 @@ use Symfony\Component\Routing\Attribute\Route; #[Route('/food/order')] final class FoodOrderController extends AbstractController { - #[Route('/', name: 'app_food_order_index', methods: ['GET'])] - public function index(FoodOrderRepository $foodOrderRepository): Response + #[Route( + path: '/list/{page}', + name: 'app_food_order_index', + requirements: [ + 'page' => '\d+', + ], + methods: ['GET'] + )] + public function index(FoodOrderRepository $foodOrderRepository, int $page = 1): Response { + $days = 4; + if ($page > 1) { + $days = 0; + } + $nextPage = $page + 1; + $prevPage = $page - 1; + $itemsPerPage = 10; + if($foodOrderRepository->count() < $page * $itemsPerPage) { + $nextPage = $page; + } + return $this->render('food_order/index.html.twig', [ - 'food_orders' => $foodOrderRepository->findLatestEntries(), + 'food_orders' => $foodOrderRepository->findLatestEntries(page: $page, pagesize: $itemsPerPage, days: $days), + 'current_page' => $page, + 'next_page' => $nextPage, + 'prev_page' => $prevPage, ]); } diff --git a/src/Repository/FoodOrderRepository.php b/src/Repository/FoodOrderRepository.php index aea76d5..c0b2405 100644 --- a/src/Repository/FoodOrderRepository.php +++ b/src/Repository/FoodOrderRepository.php @@ -3,7 +3,10 @@ namespace App\Repository; use App\Entity\FoodOrder; +use DateInterval; +use DateTimeImmutable; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Persistence\ManagerRegistry; /** @@ -27,15 +30,23 @@ final class FoodOrderRepository extends ServiceEntityRepository /** * @return FoodOrder[] */ - public function findLatestEntries(int $limit = 5): array + public function findLatestEntries(int $page = 1, int $pagesize = 10, int $days = 4): array { - $qb = $this->createQueryBuilder('alias'); - $qb->orderBy('alias.id', 'DESC'); - $qb->setMaxResults($limit); + $result = $this->createQueryBuilder('alias') + ->orderBy('alias.id', 'DESC') + ->setFirstResult(($page - 1) * $pagesize) + ->setMaxResults($pagesize) + ->getQuery() + ->getResult(); - $query = $qb->getQuery(); + if ($days < 1) { + return $result; + } - return $query->getResult(); + $date = (new DateTimeImmutable)->sub(new DateInterval('P' . $days . 'D')); + return (new ArrayCollection($result)) + ->filter(static fn(FoodOrder $order): bool => $order->getCreatedAt() >= $date) + ->getValues(); } } diff --git a/templates/food_order/index.html.twig b/templates/food_order/index.html.twig index e0aaf6c..cdd778b 100644 --- a/templates/food_order/index.html.twig +++ b/templates/food_order/index.html.twig @@ -4,7 +4,14 @@ {% block body %}

FoodOrder index

- +
+ +
+
@@ -25,11 +32,10 @@ {% endfor %}
-
- -
+ {% if prev_page > 0 %} + previous page | + {% endif %} + {% if next_page > current_page %} + next page + {% endif %} {% endblock %} diff --git a/tests/Controller/FoodOrderControllerTest.php b/tests/Controller/FoodOrderControllerTest.php index 9e72792..59eceff 100644 --- a/tests/Controller/FoodOrderControllerTest.php +++ b/tests/Controller/FoodOrderControllerTest.php @@ -41,7 +41,7 @@ final class FoodOrderControllerTest extends DbWebTest $this->manager->persist($this->vendor); $this->manager->flush(); - $crawler = $this->client->request('GET', $this->path); + $crawler = $this->client->request('GET', "{$this->path}list"); self::assertResponseStatusCodeSame(200); self::assertPageTitleContains('FoodOrder index'); $this->assertCount( @@ -61,7 +61,7 @@ final class FoodOrderControllerTest extends DbWebTest 'food_order[foodVendor]' => $this->vendor->getId(), ]); - self::assertResponseRedirects($this->path); + self::assertResponseRedirects("{$this->path}list"); self::assertSame(1, $this->repository->count([])); } } From c4cd275c8312b75c136a3b7c085b4f6e3a58c8aa Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 10 Jul 2024 19:05:28 +0200 Subject: [PATCH 02/76] #29: add test for homecontroller --- tests/Controller/HomeControllerTest.php | 69 +++++++++++++++++++++++++ tests/DbWebTest.php | 4 +- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/Controller/HomeControllerTest.php diff --git a/tests/Controller/HomeControllerTest.php b/tests/Controller/HomeControllerTest.php new file mode 100644 index 0000000..4dcb184 --- /dev/null +++ b/tests/Controller/HomeControllerTest.php @@ -0,0 +1,69 @@ +client->request( + 'GET', + '/' + ); + + self::assertResponseStatusCodeSame(302); + self::assertResponseHeaderSame('Location', '/food/order/list'); + + } + + public function testSetUsername(): void + { + $this->client->request( + 'GET', + '/username', + ); + + self::assertResponseStatusCodeSame(200); + + $this->client->submitForm('Save', [ + 'user_name_form[username]' => 'Testing-1', + ]); + self::assertResponseStatusCodeSame(302); + self::assertResponseHeaderSame('Location', '/food/order/list'); + self::assertResponseCookieValueSame('username', 'Testing-1'); + } + + + public function testRemoveUsername(): void + { + $this->client->request( + 'GET', + '/username', + ); + + self::assertResponseStatusCodeSame(200); + + $this->client->submitForm('Save', [ + 'user_name_form[username]' => '', + ]); + self::assertResponseStatusCodeSame(302); + self::assertResponseHeaderSame('Location', '/food/order/list'); + self::assertResponseCookieValueSame('username', ''); + } + + #[Override] + public function getEntityClass(): string + { + return ''; + } +} diff --git a/tests/DbWebTest.php b/tests/DbWebTest.php index 18ef24a..218ed9a 100644 --- a/tests/DbWebTest.php +++ b/tests/DbWebTest.php @@ -28,6 +28,8 @@ abstract class DbWebTest extends WebTestCase $schemaTool->dropDatabase(); $schemaTool->updateSchema($metadata); - $this->repository = $this->manager->getRepository($this->getEntityClass()); + if ($this->getEntityClass() !== '') { + $this->repository = $this->manager->getRepository($this->getEntityClass()); + } } } From 9afa7fe4314d74c6d67b7ba2167950cf3a4fce3d Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 10 Jul 2024 22:18:56 +0200 Subject: [PATCH 03/76] #29: add more tests --- composer.json | 3 +- composer.lock | 352 +++++++++++-------- src/Controller/FoodOrderController.php | 33 +- src/Entity/FoodOrder.php | 14 +- templates/food_order/index.html.twig | 16 +- tests/Controller/FoodOrderControllerTest.php | 65 ++++ tests/Controller/HomeControllerTest.php | 8 - tests/DbWebTest.php | 24 ++ 8 files changed, 331 insertions(+), 184 deletions(-) diff --git a/composer.json b/composer.json index a48327c..efc1b62 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "doctrine/doctrine-bundle": "^2.12", "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.2", + "psr/clock": "^1.0", "symfony/console": "7.1.*", "symfony/dotenv": "7.1.*", "symfony/flex": "^2", @@ -25,7 +26,7 @@ }, "require-dev": { "lubiana/code-quality": "^1.7", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^9.6.19", "symfony/browser-kit": "7.1.*", "symfony/css-selector": "7.1.*", "symfony/maker-bundle": "^1.60", diff --git a/composer.lock b/composer.lock index 02edf36..703a034 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": "ab475528135d157af8dae9e8b967be1a", + "content-hash": "2196ad236d40a68b165bf39ce4543bfc", "packages": [ { "name": "doctrine/cache", @@ -883,21 +883,21 @@ }, { "name": "doctrine/migrations", - "version": "3.7.4", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "954e0a314c2f0eb9fb418210445111747de254a6" + "reference": "535a70dcbd88b8c6ba945be050977457f4f4c06c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/954e0a314c2f0eb9fb418210445111747de254a6", - "reference": "954e0a314c2f0eb9fb418210445111747de254a6", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/535a70dcbd88b8c6ba945be050977457f4f4c06c", + "reference": "535a70dcbd88b8c6ba945be050977457f4f4c06c", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/dbal": "^3.5.1 || ^4", + "doctrine/dbal": "^3.6 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2.0", "php": "^8.1", @@ -935,7 +935,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + "Doctrine\\Migrations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -965,7 +965,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.7.4" + "source": "https://github.com/doctrine/migrations/tree/3.8.0" }, "funding": [ { @@ -981,20 +981,20 @@ "type": "tidelift" } ], - "time": "2024-03-06T13:41:11+00:00" + "time": "2024-06-26T14:12:46+00:00" }, { "name": "doctrine/orm", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332" + "reference": "722cea6536775206e81744542b36fa7c9a4ea3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/37946d3a21ddf837c0d84f8156ee60a92102e332", - "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332", + "url": "https://api.github.com/repos/doctrine/orm/zipball/722cea6536775206e81744542b36fa7c9a4ea3e5", + "reference": "722cea6536775206e81744542b36fa7c9a4ea3e5", "shasum": "" }, "require": { @@ -1067,9 +1067,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.2.0" + "source": "https://github.com/doctrine/orm/tree/3.2.1" }, - "time": "2024-05-23T14:27:52+00:00" + "time": "2024-06-26T21:48:58+00:00" }, { "name": "doctrine/persistence", @@ -1272,6 +1272,54 @@ }, "time": "2021-02-03T23:26:27+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -1427,16 +1475,16 @@ }, { "name": "symfony/cache", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "760294dc7158372699dccd077965c16c328f8719" + "reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/760294dc7158372699dccd077965c16c328f8719", - "reference": "760294dc7158372699dccd077965c16c328f8719", + "url": "https://api.github.com/repos/symfony/cache/zipball/e933e1d947ffb88efcdd34a2bd51561cab7deaae", + "reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae", "shasum": "" }, "require": { @@ -1504,7 +1552,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.1.1" + "source": "https://github.com/symfony/cache/tree/v7.1.2" }, "funding": [ { @@ -1520,7 +1568,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-11T13:32:38+00:00" }, { "name": "symfony/cache-contracts", @@ -1675,16 +1723,16 @@ }, { "name": "symfony/console", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3" + "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9b008f2d7b21c74ef4d0c3de6077a642bc55ece3", - "reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3", + "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", + "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", "shasum": "" }, "require": { @@ -1748,7 +1796,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.1" + "source": "https://github.com/symfony/console/tree/v7.1.2" }, "funding": [ { @@ -1764,20 +1812,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "77c636dfd86c0b60c5d184b2fd2ddf8dd11c309c" + "reference": "6e108cded928bdafaf1da3fabe30dd5af20e36b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/77c636dfd86c0b60c5d184b2fd2ddf8dd11c309c", - "reference": "77c636dfd86c0b60c5d184b2fd2ddf8dd11c309c", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6e108cded928bdafaf1da3fabe30dd5af20e36b9", + "reference": "6e108cded928bdafaf1da3fabe30dd5af20e36b9", "shasum": "" }, "require": { @@ -1828,7 +1876,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.1.1" + "source": "https://github.com/symfony/dependency-injection/tree/v7.1.2" }, "funding": [ { @@ -1844,7 +1892,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1915,16 +1963,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "2c36eca96f111ada35b648a4d6e8aa61f354e4d4" + "reference": "9fc4bebf69f00d4ebb12ee904d808b496035e2f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/2c36eca96f111ada35b648a4d6e8aa61f354e4d4", - "reference": "2c36eca96f111ada35b648a4d6e8aa61f354e4d4", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/9fc4bebf69f00d4ebb12ee904d808b496035e2f6", + "reference": "9fc4bebf69f00d4ebb12ee904d808b496035e2f6", "shasum": "" }, "require": { @@ -2003,7 +2051,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.1.1" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.1.2" }, "funding": [ { @@ -2019,7 +2067,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T09:27:18+00:00" }, { "name": "symfony/dotenv", @@ -2097,16 +2145,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "e9b8bbce0b4f322939332ab7b6b81d8c11da27dd" + "reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/e9b8bbce0b4f322939332ab7b6b81d8c11da27dd", - "reference": "e9b8bbce0b4f322939332ab7b6b81d8c11da27dd", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/2412d3dddb5c9ea51a39cfbff1c565fc9844ca32", + "reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32", "shasum": "" }, "require": { @@ -2152,7 +2200,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.1" + "source": "https://github.com/symfony/error-handler/tree/v7.1.2" }, "funding": [ { @@ -2168,7 +2216,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-25T19:55:06+00:00" }, { "name": "symfony/event-dispatcher", @@ -2328,16 +2376,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "802e87002f919296c9f606457d9fa327a0b3d6b2" + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/802e87002f919296c9f606457d9fa327a0b3d6b2", - "reference": "802e87002f919296c9f606457d9fa327a0b3d6b2", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", "shasum": "" }, "require": { @@ -2374,7 +2422,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.1" + "source": "https://github.com/symfony/filesystem/tree/v7.1.2" }, "funding": [ { @@ -2390,7 +2438,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/finder", @@ -2620,16 +2668,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "79a20497022b8853416e20c836ee150b754c332a" + "reference": "54a84f49658e2e87167396b2259a55e55e11f4a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/79a20497022b8853416e20c836ee150b754c332a", - "reference": "79a20497022b8853416e20c836ee150b754c332a", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/54a84f49658e2e87167396b2259a55e55e11f4a2", + "reference": "54a84f49658e2e87167396b2259a55e55e11f4a2", "shasum": "" }, "require": { @@ -2747,7 +2795,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.1.1" + "source": "https://github.com/symfony/framework-bundle/tree/v7.1.2" }, "funding": [ { @@ -2763,7 +2811,7 @@ "type": "tidelift" } ], - "time": "2024-06-04T06:40:14+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/http-foundation", @@ -2844,16 +2892,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "fa8d1c75b5f33b1302afccf81811f93976c6e26f" + "reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/fa8d1c75b5f33b1302afccf81811f93976c6e26f", - "reference": "fa8d1c75b5f33b1302afccf81811f93976c6e26f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6", + "reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6", "shasum": "" }, "require": { @@ -2938,7 +2986,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.1" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.2" }, "funding": [ { @@ -2954,7 +3002,7 @@ "type": "tidelift" } ], - "time": "2024-06-04T06:52:15+00:00" + "time": "2024-06-28T13:13:31+00:00" }, { "name": "symfony/options-resolver", @@ -3651,16 +3699,16 @@ }, { "name": "symfony/property-info", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "0f80f818c6728f15de30a4f89866d68e4912ae84" + "reference": "d7b91e4aa07e822a9b935fc29a7254c12d502f16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/0f80f818c6728f15de30a4f89866d68e4912ae84", - "reference": "0f80f818c6728f15de30a4f89866d68e4912ae84", + "url": "https://api.github.com/repos/symfony/property-info/zipball/d7b91e4aa07e822a9b935fc29a7254c12d502f16", + "reference": "d7b91e4aa07e822a9b935fc29a7254c12d502f16", "shasum": "" }, "require": { @@ -3715,7 +3763,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.1.1" + "source": "https://github.com/symfony/property-info/tree/v7.1.2" }, "funding": [ { @@ -3731,7 +3779,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-26T07:21:35+00:00" }, { "name": "symfony/routing", @@ -3895,16 +3943,16 @@ }, { "name": "symfony/security-core", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "536399671a46b0e615d69583f067e30ad25ad038" + "reference": "d615960211a11913e70f8576e5c38cd05d90ec3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/536399671a46b0e615d69583f067e30ad25ad038", - "reference": "536399671a46b0e615d69583f067e30ad25ad038", + "url": "https://api.github.com/repos/symfony/security-core/zipball/d615960211a11913e70f8576e5c38cd05d90ec3f", + "reference": "d615960211a11913e70f8576e5c38cd05d90ec3f", "shasum": "" }, "require": { @@ -3961,7 +4009,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.1.1" + "source": "https://github.com/symfony/security-core/tree/v7.1.2" }, "funding": [ { @@ -3977,7 +4025,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/security-csrf", @@ -4194,16 +4242,16 @@ }, { "name": "symfony/string", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "60bc311c74e0af215101235aa6f471bcbc032df2" + "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/60bc311c74e0af215101235aa6f471bcbc032df2", - "reference": "60bc311c74e0af215101235aa6f471bcbc032df2", + "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", + "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", "shasum": "" }, "require": { @@ -4261,7 +4309,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.1" + "source": "https://github.com/symfony/string/tree/v7.1.2" }, "funding": [ { @@ -4277,7 +4325,7 @@ "type": "tidelift" } ], - "time": "2024-06-04T06:40:14+00:00" + "time": "2024-06-28T09:27:18+00:00" }, { "name": "symfony/translation-contracts", @@ -4708,16 +4756,16 @@ }, { "name": "symfony/validator", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "fcab7598968b21c361becc930fcae8846638c4c0" + "reference": "bed12b7d5bd4dac452db5fa6203331c876b489e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/fcab7598968b21c361becc930fcae8846638c4c0", - "reference": "fcab7598968b21c361becc930fcae8846638c4c0", + "url": "https://api.github.com/repos/symfony/validator/zipball/bed12b7d5bd4dac452db5fa6203331c876b489e7", + "reference": "bed12b7d5bd4dac452db5fa6203331c876b489e7", "shasum": "" }, "require": { @@ -4785,7 +4833,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.1.1" + "source": "https://github.com/symfony/validator/tree/v7.1.2" }, "funding": [ { @@ -4801,20 +4849,20 @@ "type": "tidelift" } ], - "time": "2024-06-04T05:58:56+00:00" + "time": "2024-06-25T19:55:06+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "deb2c2b506ff6fdbb340e00b34e9901e1605f293" + "reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/deb2c2b506ff6fdbb340e00b34e9901e1605f293", - "reference": "deb2c2b506ff6fdbb340e00b34e9901e1605f293", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5857c57c6b4b86524c08cf4f4bc95327270a816d", + "reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d", "shasum": "" }, "require": { @@ -4868,7 +4916,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.1" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.2" }, "funding": [ { @@ -4884,20 +4932,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "db82c2b73b88734557cfc30e3270d83fa651b712" + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/db82c2b73b88734557cfc30e3270d83fa651b712", - "reference": "db82c2b73b88734557cfc30e3270d83fa651b712", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", "shasum": "" }, "require": { @@ -4944,7 +4992,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.1.1" + "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" }, "funding": [ { @@ -4960,7 +5008,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/yaml", @@ -5350,16 +5398,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -5370,7 +5418,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -5402,9 +5450,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -5573,16 +5621,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.11.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", "shasum": "" }, "require": { @@ -5627,7 +5675,7 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-07-06T11:17:41+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5950,45 +5998,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -6033,7 +6081,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -6049,20 +6097,20 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { "name": "rector/rector", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "c930cdb21294f10955ddfc31b720971e8333943d" + "reference": "2fa387553db22b6f9bcccf5ff16f2c2c18a52a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/c930cdb21294f10955ddfc31b720971e8333943d", - "reference": "c930cdb21294f10955ddfc31b720971e8333943d", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/2fa387553db22b6f9bcccf5ff16f2c2c18a52a65", + "reference": "2fa387553db22b6f9bcccf5ff16f2c2c18a52a65", "shasum": "" }, "require": { @@ -6100,7 +6148,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.1.1" + "source": "https://github.com/rectorphp/rector/tree/1.2.0" }, "funding": [ { @@ -6108,7 +6156,7 @@ "type": "github" } ], - "time": "2024-06-21T07:51:17+00:00" + "time": "2024-07-01T14:24:45+00:00" }, { "name": "sebastian/cli-parser", @@ -7512,16 +7560,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16" + "reference": "8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16", - "reference": "3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8", + "reference": "8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8", "shasum": "" }, "require": { @@ -7574,7 +7622,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.1" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.2" }, "funding": [ { @@ -7590,7 +7638,7 @@ "type": "tidelift" } ], - "time": "2024-06-04T06:50:37+00:00" + "time": "2024-06-25T19:55:06+00:00" }, { "name": "symfony/process", @@ -7697,16 +7745,16 @@ }, { "name": "symplify/easy-coding-standard", - "version": "12.3.0", + "version": "12.3.1", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "f919574aa566b4d00fd06700ca61168aafef66e1" + "reference": "bd670feae8d0b6da891d29a3c549bd0f4aa48711" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/f919574aa566b4d00fd06700ca61168aafef66e1", - "reference": "f919574aa566b4d00fd06700ca61168aafef66e1", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/bd670feae8d0b6da891d29a3c549bd0f4aa48711", + "reference": "bd670feae8d0b6da891d29a3c549bd0f4aa48711", "shasum": "" }, "require": { @@ -7742,7 +7790,7 @@ ], "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.3.0" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.3.1" }, "funding": [ { @@ -7754,7 +7802,7 @@ "type": "github" } ], - "time": "2024-06-18T07:35:59+00:00" + "time": "2024-07-06T12:33:15+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Controller/FoodOrderController.php b/src/Controller/FoodOrderController.php index 9fdb77d..f78cd09 100644 --- a/src/Controller/FoodOrderController.php +++ b/src/Controller/FoodOrderController.php @@ -15,28 +15,45 @@ use Symfony\Component\Routing\Attribute\Route; final class FoodOrderController extends AbstractController { #[Route( - path: '/list/{page}', + path: '/list', name: 'app_food_order_index', + methods: ['GET'] + )] + public function index(FoodOrderRepository $foodOrderRepository): Response + { + + return $this->render('food_order/index.html.twig', [ + 'food_orders' => $foodOrderRepository->findLatestEntries(days: 3), + 'current_page' => 0, + 'next_page' => 0, + 'prev_page' => 0, + ]); + } + + #[Route( + path: '/list/archive/{page}', + name: 'app_food_order_archive', requirements: [ 'page' => '\d+', ], methods: ['GET'] )] - public function index(FoodOrderRepository $foodOrderRepository, int $page = 1): Response + public function archive(FoodOrderRepository $foodOrderRepository, int $page = 1): Response { - $days = 4; - if ($page > 1) { - $days = 0; - } $nextPage = $page + 1; $prevPage = $page - 1; $itemsPerPage = 10; - if($foodOrderRepository->count() < $page * $itemsPerPage) { + $count = $foodOrderRepository->count(); + if($count < $page * $itemsPerPage) { $nextPage = $page; } return $this->render('food_order/index.html.twig', [ - 'food_orders' => $foodOrderRepository->findLatestEntries(page: $page, pagesize: $itemsPerPage, days: $days), + 'food_orders' => $foodOrderRepository->findLatestEntries( + page: $page, + pagesize: $itemsPerPage, + days: 0 + ), 'current_page' => $page, 'next_page' => $nextPage, 'prev_page' => $prevPage, diff --git a/src/Entity/FoodOrder.php b/src/Entity/FoodOrder.php index 40d7db8..fe16eac 100644 --- a/src/Entity/FoodOrder.php +++ b/src/Entity/FoodOrder.php @@ -8,19 +8,12 @@ use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Component\Uid\Ulid; #[ORM\Entity(repositoryClass: FoodOrderRepository::class)] class FoodOrder { - #[ORM\Id] - #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\Column(type: UlidType::NAME, unique: true)] - #[ORM\CustomIdGenerator(class: UlidGenerator::class)] - private Ulid|null $id = null; - #[ORM\Column(nullable: true)] private DateTimeImmutable|null $closedAt = null; @@ -39,8 +32,11 @@ class FoodOrder ])] private string|null $createdBy = 'nobody'; - public function __construct() - { + public function __construct( + #[ORM\Id] + #[ORM\Column(type: UlidType::NAME, unique: true)] + private Ulid|null $id = new Ulid + ) { $this->orderItems = new ArrayCollection; $this->open(); } diff --git a/templates/food_order/index.html.twig b/templates/food_order/index.html.twig index cdd778b..d974f87 100644 --- a/templates/food_order/index.html.twig +++ b/templates/food_order/index.html.twig @@ -25,17 +25,21 @@ {% for food_order in food_orders %} {{ include('food_order/table_row.html.twig') }} - {% else %} - - no records found - {% endfor %} + {% if food_orders|length < 10 %} + + + check the archive + for older orders + + + {% endif %} {% if prev_page > 0 %} - previous page | + previous page | {% endif %} {% if next_page > current_page %} - next page + next page {% endif %} {% endblock %} diff --git a/tests/Controller/FoodOrderControllerTest.php b/tests/Controller/FoodOrderControllerTest.php index 59eceff..6eda822 100644 --- a/tests/Controller/FoodOrderControllerTest.php +++ b/tests/Controller/FoodOrderControllerTest.php @@ -8,6 +8,7 @@ use App\Tests\DbWebTest; use Override; use Symfony\Component\DomCrawler\Crawler; +use function range; use function sprintf; final class FoodOrderControllerTest extends DbWebTest @@ -51,6 +52,58 @@ final class FoodOrderControllerTest extends DbWebTest ); } + public function testPaginatedIndex(): void + { + $this->generatePaginatedOrders(); + $crawler = $this->client->request('GET', "{$this->path}list"); + self::assertResponseStatusCodeSame(200); + self::assertPageTitleContains('FoodOrder index'); + $this->assertElementContainsCount( + $crawler, + 'td', + 1, + 'older orders' + ); + $this->assertElementContainsCount( + $crawler, + 'td', + 0, + 'next page' + ); + } + + /** + * @testWith [1, 0, 1] + * [2, 1, 1] + * [3, 1, 1] + * [4, 1, 0, 5] + */ + public function testPaginatedFirstPage(int $page, int $prevPage, int $nextPage, int $items = 10): void + { + $this->generatePaginatedOrders(); + $crawler = $this->client->request('GET', "{$this->path}list/archive/{$page}"); + self::assertResponseStatusCodeSame(200); + self::assertPageTitleContains('FoodOrder index'); + $this->assertElementContainsCount( + $crawler, + 'td', + $items, + 'nobody' + ); + $this->assertElementContainsCount( + $crawler, + 'a', + $nextPage, + 'next page' + ); + $this->assertElementContainsCount( + $crawler, + 'a', + $prevPage, + 'previous page' + ); + } + public function testNew(): void { $this->client->request('GET', sprintf('%snew', $this->path)); @@ -64,4 +117,16 @@ final class FoodOrderControllerTest extends DbWebTest self::assertResponseRedirects("{$this->path}list"); self::assertSame(1, $this->repository->count([])); } + + private function generatePaginatedOrders(): void + { + foreach (range(1, 35) as $i) { + $order = new FoodOrder($this->generateOldUlid()); + $order->setFoodVendor($this->vendor); + $order->close(); + $this->manager->persist($order); + + } + $this->manager->flush(); + } } diff --git a/tests/Controller/HomeControllerTest.php b/tests/Controller/HomeControllerTest.php index 4dcb184..821e8f4 100644 --- a/tests/Controller/HomeControllerTest.php +++ b/tests/Controller/HomeControllerTest.php @@ -2,16 +2,9 @@ namespace App\Tests\Controller; -use App\Entity\FoodOrder; -use App\Entity\FoodVendor; -use App\Entity\MenuItem; -use App\Entity\OrderItem; -use App\Repository\MenuItemRepository; use App\Tests\DbWebTest; use Override; -use function sprintf; - final class HomeControllerTest extends DbWebTest { public function testIndex(): void @@ -43,7 +36,6 @@ final class HomeControllerTest extends DbWebTest self::assertResponseCookieValueSame('username', 'Testing-1'); } - public function testRemoveUsername(): void { $this->client->request( diff --git a/tests/DbWebTest.php b/tests/DbWebTest.php index 218ed9a..c7a22f8 100644 --- a/tests/DbWebTest.php +++ b/tests/DbWebTest.php @@ -2,12 +2,18 @@ namespace App\Tests; +use DateInterval; +use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Tools\SchemaTool; use Override; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\Uid\Ulid; + +use function str_contains; abstract class DbWebTest extends WebTestCase { @@ -32,4 +38,22 @@ abstract class DbWebTest extends WebTestCase $this->repository = $this->manager->getRepository($this->getEntityClass()); } } + + protected function generateOldUlid(int $daysToSubtract = 10): Ulid + { + $date = (new DateTimeImmutable)->sub(new DateInterval('P' . $daysToSubtract . 'D')); + $ulidString = Ulid::generate($date); + return Ulid::fromString($ulidString); + } + + protected function assertElementContainsCount(Crawler $crawler, string $element, int $count, string $text): void + { + $this->assertCount( + $count, + $crawler->filter($element) + ->reduce( + static fn(Crawler $node, $i): bool => str_contains($node->text(), $text), + ) + ); + } } From a0d277464d3fe88bfc7a2ea03ab52c5a66e112d7 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 10 Jul 2024 23:39:28 +0200 Subject: [PATCH 04/76] #38: pidserspass --- config/packages/twig.php | 3 +++ public/static/img/pizza.svg | 13 +++++++++++++ src/Service/Favicon.php | 18 ++++++++++++++++++ templates/base.html.twig | 3 ++- 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 public/static/img/pizza.svg create mode 100644 src/Service/Favicon.php diff --git a/config/packages/twig.php b/config/packages/twig.php index 23db999..d08fe3f 100644 --- a/config/packages/twig.php +++ b/config/packages/twig.php @@ -5,6 +5,9 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->extension('twig', [ 'file_name_pattern' => '*.twig', + 'globals' => [ + 'favicon' => '@App\Service\Favicon', + ], ]); if ($containerConfigurator->env() === 'test') { $containerConfigurator->extension('twig', [ diff --git a/public/static/img/pizza.svg b/public/static/img/pizza.svg new file mode 100644 index 0000000..f2eafa4 --- /dev/null +++ b/public/static/img/pizza.svg @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/src/Service/Favicon.php b/src/Service/Favicon.php new file mode 100644 index 0000000..3c63661 --- /dev/null +++ b/src/Service/Favicon.php @@ -0,0 +1,18 @@ + {% block title %}Welcome!{% endblock %} - + ")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 676275b..cfab487 100644 --- a/symfony.lock +++ b/symfony.lock @@ -115,6 +115,21 @@ "phpcs.xml.dist" ] }, + "symfony/asset-mapper": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, "symfony/console": { "version": "7.3", "recipe": { @@ -265,5 +280,8 @@ "config/packages/web_profiler.yaml", "config/routes/web_profiler.yaml" ] + }, + "twig/extra-bundle": { + "version": "v3.21.0" } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 1036cc3..942c876 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -3,27 +3,12 @@ - - {% block title %}Welcome!{% endblock %} - {% set currentDate = "now"|date("d") %} - {% if currentDate % 4 == 0 %} - - {% elseif currentDate % 4 == 1 %} - - {% elseif currentDate % 4 == 2 %} - - {% else %} - - {% endif %} - - + {% block javascripts %} + {% block importmap %}{{ importmap('app') }}{% endblock %} + {% endblock %}
From 5cb66c5012635b1fc026e3cca8c8c9a728a229f0 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 Jun 2025 20:12:17 +0200 Subject: [PATCH 65/76] booty --- assets/app.js | 15 +- assets/javascript/emoji-footprint.js | 19 + assets/javascript/modes.js | 136 +++++ assets/javascript/numberInputs.js | 55 ++ assets/javascript/radioState.js | 35 ++ assets/javascript/theme.js | 18 + assets/styles/app.css | 182 +++++- assets/styles/emoji-footprint.css | 30 + assets/styles/modes.css | 565 ++++++++++++++++++ config/packages/twig.php | 19 +- templates/_form.html.twig | 6 +- templates/base.html.twig | 56 +- templates/food_order/edit.html.twig | 8 +- templates/food_order/index.html.twig | 25 +- templates/food_order/new.html.twig | 4 +- templates/food_order/show.html.twig | 32 +- templates/food_order/table_row.html.twig | 2 +- templates/food_vendor/_form.html.twig | 6 +- templates/food_vendor/edit.html.twig | 8 +- templates/food_vendor/index.html.twig | 14 +- templates/food_vendor/new.html.twig | 8 +- templates/food_vendor/show.html.twig | 21 +- templates/menu_item/_delete_form.html.twig | 2 +- templates/menu_item/_form.html.twig | 6 +- templates/menu_item/edit.html.twig | 10 +- templates/menu_item/index.html.twig | 14 +- templates/menu_item/new.html.twig | 8 +- templates/menu_item/show.html.twig | 18 +- templates/order_item/_form.html.twig | 6 +- templates/order_item/edit.html.twig | 8 +- templates/order_item/new.html.twig | 16 +- templates/username.html.twig | 8 +- .../Controller/FoodOrderControllerTest.php | 6 +- .../Controller/FoodVendorControllerTest.php | 2 +- 34 files changed, 1236 insertions(+), 132 deletions(-) create mode 100644 assets/javascript/emoji-footprint.js create mode 100644 assets/javascript/modes.js create mode 100644 assets/javascript/numberInputs.js create mode 100644 assets/javascript/radioState.js create mode 100644 assets/javascript/theme.js create mode 100644 assets/styles/emoji-footprint.css create mode 100644 assets/styles/modes.css diff --git a/assets/app.js b/assets/app.js index 321cea2..88672ac 100644 --- a/assets/app.js +++ b/assets/app.js @@ -4,6 +4,19 @@ * This file will be included onto the page via the importmap() Twig function, * which should already be in your base.html.twig. */ +import 'bootstrap/dist/css/bootstrap.min.css'; import './styles/app.css'; +import './styles/modes.css'; +import './styles/emoji-footprint.css'; -import './javascript/htmx.js'; \ No newline at end of file +// Import modules +import './javascript/theme.js'; +import './javascript/emoji-footprint.js'; +import './javascript/modes.js'; +import './javascript/htmx.js'; +import 'bootstrap'; +import { initRadioState } from './javascript/radioState.js'; + +document.addEventListener('DOMContentLoaded', () => { + initRadioState(); +}); \ No newline at end of file diff --git a/assets/javascript/emoji-footprint.js b/assets/javascript/emoji-footprint.js new file mode 100644 index 0000000..238377b --- /dev/null +++ b/assets/javascript/emoji-footprint.js @@ -0,0 +1,19 @@ +// Sparkle effect on mouse move +document.addEventListener('mousemove', function (e) { + const emojis = ['✨', '💖', '🌟', '💅', '🦄', '🎉', '🌈']; + const sparkle = document.createElement('div'); + sparkle.className = 'emoji-footprint'; + sparkle.textContent = emojis[Math.floor(Math.random() * emojis.length)]; + sparkle.style.left = e.pageX + 'px'; + sparkle.style.top = e.pageY + 'px'; + document.body.appendChild(sparkle); + + setTimeout(() => { + sparkle.remove(); + }, 1000); +}); + +export function initEmojiFootprint() { + // The sparkle effect is already initialized when this module is imported + // This function can be used if we need to control when the effect starts +} \ No newline at end of file diff --git a/assets/javascript/modes.js b/assets/javascript/modes.js new file mode 100644 index 0000000..a288f32 --- /dev/null +++ b/assets/javascript/modes.js @@ -0,0 +1,136 @@ +// Bonkers mode functionality +function setEmojiLevelClass(mode) { + document.body.classList.remove('emoji-normal', 'emoji-enhanced', 'emoji-bonkers'); + if (mode === 'bonkers') { + document.body.classList.add('emoji-bonkers'); + } else if (mode === 'enhanced') { + document.body.classList.add('emoji-enhanced'); + } else { + document.body.classList.add('emoji-normal'); + } +} + +function initBonkersMode() { + // Check if we're in bonkers mode + const currentMode = document.documentElement.getAttribute('data-website-mode'); + setEmojiLevelClass(currentMode); + + if (currentMode === 'bonkers') { + // Apply bonkers mode immediately + document.body.classList.add('bonkers-mode'); + + // Start the fabulous effects + createExtraSparkles(); + createSlayEffects(); + + console.log('🌈✨ Bonkers mode activated! ✨🌈'); + } else { + // Remove bonkers mode if it was active + document.body.classList.remove('bonkers-mode'); + } +} + +// Function to create extra sparkles during bonkers mode +function createExtraSparkles() { + const currentMode = document.documentElement.getAttribute('data-website-mode'); + if (currentMode !== 'bonkers') return; + + const extraEmojis = [ + '💃', '🕺', + '🍑', '💦', '😏', '😈', '👅', '💋', '🥵', '😳', '🤤', '😍', '🥴', + '💕', '💖', '💗', '💘', '💝', '💞', '💟', '💌', '💏', '💑', + '🍆', '🥒', '🍌', '💦', '👀', '😉', '😌', '😍', '🥰', '😘', + '😚', '😋', '😏', '😫', '😩', '🥺', '🥵', '🥴', + '💖', '💗', '💕', '💞', '💓', '💗', '💖', '💘', '💝', + '💋', '💏', '💑' + ]; + const sparkle = document.createElement('div'); + sparkle.className = 'emoji-footprint'; + sparkle.textContent = extraEmojis[Math.floor(Math.random() * extraEmojis.length)]; + sparkle.style.left = Math.random() * window.innerWidth + 'px'; + sparkle.style.top = Math.random() * window.innerHeight + 'px'; + document.body.appendChild(sparkle); + + setTimeout(() => { + if (sparkle.parentNode) { + sparkle.remove(); + } + }, 3000); + + // Continue creating extra sparkles while in bonkers mode + const newMode = document.documentElement.getAttribute('data-website-mode'); + if (newMode === 'bonkers') { + setTimeout(() => createExtraSparkles(), 150); + } +} + +// Function to create slay effects +function createSlayEffects() { + const currentMode = document.documentElement.getAttribute('data-website-mode'); + if (currentMode !== 'bonkers') return; + + // Create floating "SLAY" text effects + const slayWords = [ + 'SLAY', 'QUEEN', 'FABULOUS', 'ICONIC', 'LEGENDARY', 'STUNNING', 'GORGEOUS', 'FLAWLESS', + 'DAZZLING', 'RADIANT', 'BREATHTAKING', 'EXQUISITE', 'DIVINE' + ]; + const slayElement = document.createElement('div'); + slayElement.className = 'slay-text'; + slayElement.textContent = slayWords[Math.floor(Math.random() * slayWords.length)]; + slayElement.style.left = Math.random() * window.innerWidth + 'px'; + slayElement.style.top = Math.random() * window.innerHeight + 'px'; + document.body.appendChild(slayElement); + + setTimeout(() => { + if (slayElement.parentNode) { + slayElement.remove(); + } + }, 3000); + + // Continue creating slay effects while in bonkers mode + const newMode = document.documentElement.getAttribute('data-website-mode'); + if (newMode === 'bonkers') { + setTimeout(() => createSlayEffects(), 800); + } +} + +// Watch for mode changes +function watchModeChanges() { + // Create a MutationObserver to watch for changes to the data-website-mode attribute + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-website-mode') { + const newMode = document.documentElement.getAttribute('data-website-mode'); + + if (newMode === 'bonkers') { + document.body.classList.add('bonkers-mode'); + setEmojiLevelClass(newMode); + + // Start the fabulous effects + createExtraSparkles(); + createSlayEffects(); + + console.log('🌈✨ Switched to bonkers mode! ✨🌈'); + } else { + document.body.classList.remove('bonkers-mode'); + setEmojiLevelClass(newMode); + console.log(`😴 Switched to ${newMode} mode`); + } + } + }); + }); + + // Start observing + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-website-mode'] + }); +} + +// Initialize when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + initBonkersMode(); + watchModeChanges(); +}); + +export { initBonkersMode, watchModeChanges }; diff --git a/assets/javascript/numberInputs.js b/assets/javascript/numberInputs.js new file mode 100644 index 0000000..a4a8dc1 --- /dev/null +++ b/assets/javascript/numberInputs.js @@ -0,0 +1,55 @@ +// Function to initialize number input buttons +function initNumberInputs(container = document) { + container.querySelectorAll('.number-input-wrapper').forEach(function(wrapper) { + const input = wrapper.querySelector('input[type="number"]'); + const decreaseBtn = wrapper.querySelector('[data-action="decrease"]'); + const increaseBtn = wrapper.querySelector('[data-action="increase"]'); + + if (!input || !decreaseBtn || !increaseBtn) return; + + // Skip if already initialized + if (decreaseBtn.hasAttribute('data-initialized')) return; + + const step = parseFloat(input.getAttribute('step')) || 1; + const min = 0; + const max = input.getAttribute('max') ? parseFloat(input.getAttribute('max')) : null; + + decreaseBtn.addEventListener('click', function() { + const currentValue = parseFloat(input.value) || 0; + const newValue = currentValue - step; + + if (min === null || newValue >= min) { + input.value = newValue; + input.dispatchEvent(new Event('change', { bubbles: true })); + } + }); + + increaseBtn.addEventListener('click', function() { + const currentValue = parseFloat(input.value) || 0; + const newValue = currentValue + step; + + if (max === null || newValue <= max) { + input.value = newValue; + input.dispatchEvent(new Event('change', { bubbles: true })); + } + }); + + // Validate input on change + input.addEventListener('input', function() { + const value = parseFloat(this.value); + + if (min !== null && value < min) { + this.value = min; + } + if (max !== null && value > max) { + this.value = max; + } + }); + + // Mark as initialized + decreaseBtn.setAttribute('data-initialized', 'true'); + increaseBtn.setAttribute('data-initialized', 'true'); + }); +} + +export { initNumberInputs }; \ No newline at end of file diff --git a/assets/javascript/radioState.js b/assets/javascript/radioState.js new file mode 100644 index 0000000..a30d311 --- /dev/null +++ b/assets/javascript/radioState.js @@ -0,0 +1,35 @@ +// Radio button state management with localStorage +function initRadioState() { + // Store and retrieve radio button state + const radioButtons = document.querySelectorAll('input[name="mode"]'); + + // Load saved state on page load + const savedMode = localStorage.getItem('selectedMode'); + if (savedMode) { + const radioToCheck = document.getElementById(savedMode); + if (radioToCheck) { + radioToCheck.checked = true; + // Set the data attribute to match the saved mode + document.documentElement.setAttribute('data-website-mode', savedMode); + } + } else { + // If no saved state, set to the currently checked radio button + const checkedRadio = document.querySelector('input[name="mode"]:checked'); + if (checkedRadio) { + document.documentElement.setAttribute('data-website-mode', checkedRadio.id); + } + } + + // Save state when radio button changes + radioButtons.forEach(radio => { + radio.addEventListener('change', function() { + if (this.checked) { + localStorage.setItem('selectedMode', this.id); + // Update the data attribute when mode changes + document.documentElement.setAttribute('data-website-mode', this.id); + } + }); + }); +} + +export { initRadioState }; \ No newline at end of file diff --git a/assets/javascript/theme.js b/assets/javascript/theme.js new file mode 100644 index 0000000..8acf738 --- /dev/null +++ b/assets/javascript/theme.js @@ -0,0 +1,18 @@ +// Theme detection and switching +const getPreferredTheme = () => { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +const setTheme = theme => { + document.documentElement.setAttribute('data-bs-theme', theme) +} + +// Set initial theme +setTheme(getPreferredTheme()) + +// Listen for changes in user's preferred color scheme +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + setTheme(getPreferredTheme()) +}) + +export { getPreferredTheme, setTheme }; \ No newline at end of file diff --git a/assets/styles/app.css b/assets/styles/app.css index dd6181a..7400735 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -1,3 +1,179 @@ -body { - background-color: skyblue; -} +/* + * ================================================================================================= + * 💖 BUBBLEGUM PUNK THEME (LIGHT) 💖 + * + * This isn't just a theme. It's a statement. + * Unapologetically loud, pink, and quirky. + * ================================================================================================= + */ + :root, + [data-bs-theme=light] { + /* --- CORE VIBE --- */ + --bs-pink: #FF007A; /* 💖 Hyper Pink (Our Queen) */ + --bs-green: #CFFF50; /* 🧪 Toxic Slime */ + --bs-purple: #A328D6; /* 👾 Graffiti Purple */ + --bs-yellow: #F9F871; /* ⚡ Neon Lemon */ + --bs-cyan: #00F5D4; /* 💎 Glitchy Teal */ + --bs-blue: #00A9E0; /* 💦 Splash Zone */ + + /* Let's redefine ALL the core colors to match the new energy */ + --bs-primary: var(--bs-pink); + --bs-secondary: var(--bs-green); + --bs-success: var(--bs-cyan); + --bs-info: var(--bs-blue); + --bs-warning: var(--bs-yellow); + --bs-danger: #FF3D3D; /* 🚨 Code Red Rave */ + + /* --- BACKGROUNDS & TEXT --- */ + /* No more boring white! */ + --bs-body-bg: #FFF5FD; /* A soft, dreamy pink canvas */ + --bs-body-color: #4A003D; /* Dark Plum (instead of black) for text */ + --bs-heading-color: var(--bs-purple); /* Make headings POP */ + --bs-secondary-color: rgba(74, 0, 61, 0.75); /* Plum, but softer */ + --bs-tertiary-color: rgba(74, 0, 61, 0.5); + + /* Make cards and containers pure white to contrast the pink background */ + --bs-tertiary-bg: #FFFFFF; + --bs-secondary-bg: #FEF9FE; + + /* --- LINKS & CODE --- */ + --bs-link-color: var(--bs-pink); + --bs-link-hover-color: var(--bs-purple); + --bs-code-color: var(--bs-purple); + + /* --- BORDERS & SHADOWS: LET'S GET QUIRKY --- */ + --bs-border-width: 2px; /* Chunky borders! */ + --bs-border-color: #FFD6F5; /* Pink-tinted border color */ + --bs-border-color-translucent: rgba(74, 0, 61, 0.2); + --bs-border-radius: 1rem; /* Super bubbly and round */ + --bs-border-radius-sm: 0.5rem; + --bs-border-radius-lg: 1.5rem; + --bs-border-radius-pill: 50rem; + + /* Say goodbye to black shadows, hello to colored glows! */ + --bs-box-shadow: 0 4px 12px rgba(255, 0, 122, 0.2); + --bs-box-shadow-sm: 0 2px 4px rgba(255, 0, 122, 0.15); + --bs-box-shadow-lg: 0 8px 30px rgba(255, 0, 122, 0.25); + --bs-box-shadow-inset: inset 0 1px 4px rgba(74, 0, 61, 0.2); + + /* --- THE GRADIENT: THE SOUL OF THE THEME --- */ + --bs-gradient: linear-gradient(75deg, var(--bs-primary), var(--bs-secondary)); + + /* --- Don't forget the RGB values for Bootstrap components! --- */ + --bs-primary-rgb: 255, 0, 122; + --bs-secondary-rgb: 207, 255, 80; + --bs-body-color-rgb: 74, 0, 61; + --bs-body-bg-rgb: 255, 245, 253; + } + + + /* + * ================================================================================================= + * 🌙🦇 CYBER GOTH THEME (DARK) 🦇🌙 + * + * The lights are out, the neon is ON. + * A dark, moody theme with vibrant, glowing accents. + * ================================================================================================= + */ + [data-bs-theme=dark] { + color-scheme: dark; + + /* --- BACKGROUNDS & TEXT --- */ + --bs-body-bg: #1D001A; /* Deep, dark space purple */ + --bs-body-color: #FFE9FA; /* Light pink text for high contrast */ + --bs-heading-color: var(--bs-cyan); /* Glowing cyan headings */ + + --bs-tertiary-bg: #2E0028; /* A slightly lighter container background */ + --bs-secondary-bg: #3A0033; + --bs-secondary-color: rgba(255, 233, 250, 0.75); + --bs-tertiary-color: rgba(255, 233, 250, 0.5); + + /* --- LINKS & CODE --- */ + /* Using the Toxic Slime for links gives it that cyber look */ + --bs-link-color: var(--bs-green); + --bs-link-hover-color: var(--bs-cyan); + --bs-code-color: var(--bs-pink); + + /* --- BORDERS & SHADOWS: NEON GLOWS --- */ + --bs-border-color: #5C004F; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + + /* Redefine shadows to be neon glows */ + --bs-box-shadow: 0 0 15px rgba(var(--bs-primary-rgb), 0.4); + --bs-box-shadow-lg: 0 0 30px rgba(var(--bs-primary-rgb), 0.5); + + /* --- EMPHASIS & SUBTLE BACKGROUNDS --- */ + /* These are for alerts, badges, etc. They'll be dark with glowing text. */ + --bs-primary-text-emphasis: #FF8AD1; + --bs-secondary-text-emphasis: #E2FF8A; + --bs-success-text-emphasis: #8AFFEB; + --bs-info-text-emphasis: #7ADCF5; + --bs-warning-text-emphasis: #FAF8A8; + --bs-danger-text-emphasis: #FF8A8A; + + --bs-primary-bg-subtle: #3D002B; + --bs-secondary-bg-subtle: #415215; + --bs-success-bg-subtle: #00332B; + --bs-info-bg-subtle: #00313D; + --bs-warning-bg-subtle: #3E3D1C; + --bs-danger-bg-subtle: #520E0E; + } + +/* === EMOJI LEVELS === */ +.emoji-normal .emoji-normal { display: inline; } +.emoji-normal .emoji-enhanced, +.emoji-normal .emoji-bonkers { display: none; } + +.emoji-enhanced .emoji-enhanced { display: inline; } +.emoji-enhanced .emoji-normal, +.emoji-enhanced .emoji-bonkers { display: none; } + +.emoji-bonkers .emoji-bonkers { display: inline; } +.emoji-bonkers .emoji-normal, +.emoji-bonkers .emoji-enhanced { display: none; } + /* + * ================================================================================================= + * 🌈 RAINBOW PRIDE ELEMENTS 🌈 + * + * Fabulous rainbow-themed elements to celebrate diversity and pride! + * ================================================================================================= + */ + .bg-rainbow { + background: linear-gradient( + to right, + #FF5757, /* Red */ + #FFBD59, /* Orange */ + #F9F871, /* Yellow */ + #CFFF50, /* Green */ + #00F5D4, /* Teal */ + #00A9E0, /* Blue */ + #A328D6 /* Purple */ + ); + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); + font-weight: bold; + border: none; + } + + .fun-fact { + font-size: 1.1rem; + line-height: 1.6; + font-style: italic; + } + + /* Add a subtle rainbow border to the fun facts card */ + .card:has(.fun-fact) { + border-width: 2px; + border-style: solid; + border-image: linear-gradient( + to right, + #FF5757, /* Red */ + #FFBD59, /* Orange */ + #F9F871, /* Yellow */ + #CFFF50, /* Green */ + #00F5D4, /* Teal */ + #00A9E0, /* Blue */ + #A328D6 /* Purple */ + ) 1; + box-shadow: 0 4px 15px rgba(163, 40, 214, 0.2); + } diff --git a/assets/styles/emoji-footprint.css b/assets/styles/emoji-footprint.css new file mode 100644 index 0000000..b076361 --- /dev/null +++ b/assets/styles/emoji-footprint.css @@ -0,0 +1,30 @@ + +/* Emoji Footprint Animation */ +.emoji-footprint { + position: absolute; + font-size: 1.6rem; + pointer-events: none; + animation: emojiFade 1s ease-out forwards; + transform: translate(-50%, -50%) scale(1); + opacity: 1; + z-index: 9999; + text-shadow: + 0 0 4px #ff00bf, + 0 0 8px #ff80df, + 0 0 12px #ffccff; +} + +@keyframes emojiFade { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } + 50% { + transform: translate(-50%, -50%) scale(1.5); + opacity: 0.7; + } + 100% { + transform: translate(-50%, -50%) scale(2); + opacity: 0; + } +} \ No newline at end of file diff --git a/assets/styles/modes.css b/assets/styles/modes.css new file mode 100644 index 0000000..383815b --- /dev/null +++ b/assets/styles/modes.css @@ -0,0 +1,565 @@ +/* 🌈✨ BONKERS MODE ANIMATIONS ✨🌈 */ +@keyframes rainbowGradient { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +@keyframes discoFlash { + 0%, 100% { + background-color: var(--bs-pink); + box-shadow: 0 0 20px var(--bs-pink), 0 0 40px var(--bs-pink); + } + 16.66% { + background-color: var(--bs-purple); + box-shadow: 0 0 20px var(--bs-purple), 0 0 40px var(--bs-purple); + } + 33.33% { + background-color: var(--bs-cyan); + box-shadow: 0 0 20px var(--bs-cyan), 0 0 40px var(--bs-cyan); + } + 50% { + background-color: var(--bs-yellow); + box-shadow: 0 0 20px var(--bs-yellow), 0 0 40px var(--bs-yellow); + } + 66.66% { + background-color: var(--bs-green); + box-shadow: 0 0 20px var(--bs-green), 0 0 40px var(--bs-green); + } + 83.33% { + background-color: var(--bs-orange); + box-shadow: 0 0 20px var(--bs-orange), 0 0 40px var(--bs-orange); + } +} + +@keyframes wiggle { + 0%, 100% { transform: rotate(0deg); } + 25% { transform: rotate(-2deg); } + 75% { transform: rotate(2deg); } +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes rainbowText { + 0% { color: var(--bs-red); } + 14.28% { color: var(--bs-orange); } + 28.57% { color: var(--bs-yellow); } + 42.85% { color: var(--bs-green); } + 57.14% { color: var(--bs-cyan); } + 71.42% { color: var(--bs-purple); } + 85.71% { color: var(--bs-pink); } + 100% { color: var(--bs-red); } +} + +@keyframes shine { + 0% { left: -100%; } + 50% { left: 100%; } + 100% { left: 100%; } +} + +@keyframes slayFloat { + 0% { + transform: translateY(0) scale(0.5); + opacity: 0; + } + 20% { + transform: translateY(-20px) scale(1); + opacity: 1; + } + 80% { + transform: translateY(-60px) scale(1.2); + opacity: 0.8; + } + 100% { + transform: translateY(-100px) scale(1.5); + opacity: 0; + } +} + +/* 🎭 BONKERS MODE CLASSES 🎭 */ +.bonkers-mode { + transition: all 0.3s ease-in-out; +} + +.bonkers-mode .btn { + animation: discoFlash 0.3s infinite, wiggle 0.2s infinite; + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red)); + background-size: 400% 400%; + animation: discoFlash 0.3s infinite, wiggle 0.2s infinite, rainbowGradient 1s ease infinite; + border: 4px solid var(--bs-white); + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + position: relative; + overflow: hidden; + transition: all 0.2s ease; +} + +.bonkers-mode .btn:hover { + animation: discoFlash 0.2s infinite, wiggle 0.1s infinite, rainbowGradient 0.5s ease infinite; + box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple); +} + +.bonkers-mode .btn::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent); + transform: rotate(45deg); + animation: spin 0.5s linear infinite; +} + +.bonkers-mode .navbar { + background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red)); + background-size: 200% 200%; + animation: rainbowGradient 2s ease infinite; + box-shadow: 0 0 50px rgba(255, 105, 180, 0.9); + height: auto !important; + min-height: 56px; +} + +.bonkers-mode .navbar-brand { + animation: rainbowText 0.8s infinite, wiggle 0.4s infinite; + font-size: 1.8em; + text-shadow: 3px 3px 6px rgba(0,0,0,0.5); + position: relative; + overflow: hidden; +} + +.bonkers-mode .navbar-brand::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent); + transform: rotate(45deg); + animation: spin 2s linear infinite; +} + +.bonkers-mode .navbar-nav .nav-link { + animation: rainbowText 1.2s infinite, wiggle 0.3s infinite; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border: 2px solid transparent; + border-radius: 8px; + padding: 8px 16px; + margin: 0 4px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.bonkers-mode .navbar-nav .nav-link::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + animation: shine 1.5s ease-in-out infinite; +} + +.bonkers-mode .navbar-nav .nav-link:hover { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + border-color: var(--bs-white); + box-shadow: 0 0 20px var(--bs-pink); + animation: discoFlash 0.5s infinite, wiggle 0.2s infinite; +} + +.bonkers-mode .navbar-nav .nav-link.active { + background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange)); + border-color: var(--bs-white); + box-shadow: 0 0 25px var(--bs-yellow); + animation: discoFlash 0.8s infinite, wiggle 0.3s infinite; +} + +.bonkers-mode .navbar-text { + animation: rainbowText 1.5s infinite, wiggle 0.5s infinite; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border: 2px solid var(--bs-white); + border-radius: 8px; + padding: 6px 12px; + background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue)); + box-shadow: 0 0 15px var(--bs-cyan); +} + +.bonkers-mode .navbar-toggler { + border: 3px solid var(--bs-white); + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + animation: discoFlash 0.6s infinite, wiggle 0.4s infinite; + box-shadow: 0 0 20px var(--bs-pink); +} + +.bonkers-mode .navbar-toggler:focus { + box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5); +} + +.bonkers-mode .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + animation: spin 1s linear infinite; +} + +.bonkers-mode .dropdown-menu { + background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan)); + border: 3px solid var(--bs-white); + box-shadow: 0 0 30px rgba(255,105,180,0.8); + animation: rainbowGradient 2s ease infinite; +} + +.bonkers-mode .dropdown-item { + animation: rainbowText 1.8s infinite, wiggle 0.6s infinite; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); + border-bottom: 1px solid rgba(255,255,255,0.3); + transition: all 0.3s ease; +} + +.bonkers-mode .dropdown-item:hover { + background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange)); + color: var(--bs-white); + box-shadow: 0 0 15px var(--bs-yellow); + animation: discoFlash 0.5s infinite, wiggle 0.3s infinite; +} + +.bonkers-mode .navbar-collapse { + background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1)); + border-radius: 8px; + margin-top: 8px; + padding: 8px; + border: 2px solid var(--bs-pink); +} + +.bonkers-mode h1, .bonkers-mode h2, .bonkers-mode h3 { + animation: rainbowText 1.5s infinite; + text-shadow: 2px 2px 4px rgba(0,0,0,0.3); +} + +.bonkers-mode .table { + background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2)); + animation: rainbowGradient 3s ease infinite; + border: 3px solid var(--bs-pink); + box-shadow: 0 0 30px rgba(255,105,180,0.5); +} + +.bonkers-mode .table th { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + color: var(--bs-white); + animation: discoFlash 0.8s infinite; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); + font-size: 1.1em; +} + +.bonkers-mode .form-control { + border: 3px solid var(--bs-pink); + box-shadow: 0 0 15px var(--bs-pink); + animation: pulse 0.6s infinite; +} + +.bonkers-mode .alert { + animation: discoFlash 0.6s infinite, wiggle 0.3s infinite; + border: 4px solid var(--bs-white); + font-weight: bold; + font-size: 1.1em; +} + +.bonkers-mode .card { + background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2)); + border: 3px solid var(--bs-purple); + box-shadow: 0 0 35px rgba(138,43,226,0.6); + animation: pulse 1s infinite; +} + +.bonkers-mode .modal-content { + background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan)); + border: 4px solid var(--bs-white); + box-shadow: 0 0 50px rgba(255,105,180,0.8); + animation: rainbowGradient 2s ease infinite; +} + +.bonkers-mode .modal-header { + background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange)); + animation: discoFlash 0.8s infinite; + font-size: 1.2em; +} + +.bonkers-mode .number-input-wrapper { + animation: wiggle 0.4s infinite; +} + +.bonkers-mode .number-input-wrapper .btn { + animation: discoFlash 0.3s infinite, wiggle 0.2s infinite; +} + +/* Enhanced mode styles (for future use) */ +[data-website-mode="enhanced"] .btn { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red)); + background-size: 400% 400%; + animation: rainbowGradient 1s ease infinite; + border: 4px solid var(--bs-white); + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + position: relative; + overflow: hidden; + transition: all 0.2s ease; +} + +[data-website-mode="enhanced"] .btn:hover { + animation: rainbowGradient 0.5s ease infinite; + box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple); +} + +[data-website-mode="enhanced"] .btn::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent); + transform: rotate(45deg); + animation: spin 0.5s linear infinite; +} + +[data-website-mode="enhanced"] .navbar { + background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red)); + background-size: 200% 200%; + animation: rainbowGradient 2s ease infinite; + box-shadow: 0 0 50px rgba(255, 105, 180, 0.9); + height: auto !important; + min-height: 56px; +} + +[data-website-mode="enhanced"] .navbar-brand { + animation: rainbowText 0.8s infinite; + font-size: 1.8em; + text-shadow: 3px 3px 6px rgba(0,0,0,0.5); + position: relative; + overflow: hidden; +} + +[data-website-mode="enhanced"] .navbar-brand::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent); + transform: rotate(45deg); + animation: spin 2s linear infinite; +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link { + animation: rainbowText 1.2s infinite; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border: 2px solid transparent; + border-radius: 8px; + padding: 8px 16px; + margin: 0 4px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + animation: shine 1.5s ease-in-out infinite; +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link:hover { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + border-color: var(--bs-white); + box-shadow: 0 0 20px var(--bs-pink); +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link.active { + background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange)); + border-color: var(--bs-white); + box-shadow: 0 0 25px var(--bs-yellow); +} + +[data-website-mode="enhanced"] .navbar-text { + animation: rainbowText 1.5s infinite; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border: 2px solid var(--bs-white); + border-radius: 8px; + padding: 6px 12px; + background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue)); + box-shadow: 0 0 15px var(--bs-cyan); +} + +[data-website-mode="enhanced"] .navbar-toggler { + border: 3px solid var(--bs-white); + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + animation: rainbowGradient 0.6s ease infinite; + box-shadow: 0 0 20px var(--bs-pink); +} + +[data-website-mode="enhanced"] .navbar-toggler:focus { + box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5); +} + +[data-website-mode="enhanced"] .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + animation: spin 1s linear infinite; +} + +[data-website-mode="enhanced"] .dropdown-menu { + background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan)); + border: 3px solid var(--bs-white); + box-shadow: 0 0 30px rgba(255,105,180,0.8); + animation: rainbowGradient 2s ease infinite; +} + +[data-website-mode="enhanced"] .dropdown-item { + animation: rainbowText 1.8s infinite; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); + border-bottom: 1px solid rgba(255,255,255,0.3); + transition: all 0.3s ease; +} + +[data-website-mode="enhanced"] .dropdown-item:hover { + background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange)); + color: var(--bs-white); + box-shadow: 0 0 15px var(--bs-yellow); +} + +[data-website-mode="enhanced"] .navbar-collapse { + background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1)); + border-radius: 8px; + margin-top: 8px; + padding: 8px; + border: 2px solid var(--bs-pink); +} + +[data-website-mode="enhanced"] h1, [data-website-mode="enhanced"] h2, [data-website-mode="enhanced"] h3 { + animation: rainbowText 1.5s infinite; + text-shadow: 2px 2px 4px rgba(0,0,0,0.3); +} + +[data-website-mode="enhanced"] .table { + background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2)); + animation: rainbowGradient 3s ease infinite; + border: 3px solid var(--bs-pink); + box-shadow: 0 0 30px rgba(255,105,180,0.5); +} + +[data-website-mode="enhanced"] .table th { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + color: var(--bs-white); + animation: rainbowGradient 0.8s ease infinite; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); + font-size: 1.1em; +} + +[data-website-mode="enhanced"] .form-control { + border: 3px solid var(--bs-pink); + box-shadow: 0 0 15px var(--bs-pink); +} + +[data-website-mode="enhanced"] .alert { + animation: rainbowGradient 0.6s ease infinite; + border: 4px solid var(--bs-white); + font-weight: bold; + font-size: 1.1em; +} + +[data-website-mode="enhanced"] .card { + background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2)); + border: 3px solid var(--bs-purple); + box-shadow: 0 0 35px rgba(138,43,226,0.6); +} + +[data-website-mode="enhanced"] .modal-content { + background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan)); + border: 4px solid var(--bs-white); + box-shadow: 0 0 50px rgba(255,105,180,0.8); + animation: rainbowGradient 2s ease infinite; +} + +[data-website-mode="enhanced"] .modal-header { + background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange)); + animation: rainbowGradient 0.8s ease infinite; + font-size: 1.2em; +} + +[data-website-mode="enhanced"] .number-input-wrapper { +} + +[data-website-mode="enhanced"] .number-input-wrapper .btn { + animation: rainbowGradient 0.3s ease infinite; +} + +/* Emoji Footprint Animation */ +.emoji-footprint { + position: absolute; + font-size: 1.6rem; + pointer-events: none; + animation: emojiFade 1s ease-out forwards; + transform: translate(-50%, -50%) scale(1); + opacity: 1; + z-index: 9999; + text-shadow: + 0 0 4px #ff00bf, + 0 0 8px #ff80df, + 0 0 12px #ffccff; +} + +@keyframes emojiFade { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } + 50% { + transform: translate(-50%, -50%) scale(1.5); + opacity: 0.7; + } + 100% { + transform: translate(-50%, -50%) scale(2); + opacity: 0; + } +} + +/* 💅 SLAY TEXT EFFECTS 💅 */ +.slay-text { + position: fixed; + font-size: 2rem; + font-weight: bold; + pointer-events: none; + z-index: 10000; + animation: slayFloat 3s ease-out forwards; + text-shadow: + 0 0 10px #ff00bf, + 0 0 20px #ff80df, + 0 0 30px #ffccff, + 2px 2px 4px rgba(0,0,0,0.5); + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow)); + background-size: 400% 400%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: slayFloat 3s ease-out forwards, rainbowGradient 1s ease infinite; +} diff --git a/config/packages/twig.php b/config/packages/twig.php index d08fe3f..1c06a5b 100644 --- a/config/packages/twig.php +++ b/config/packages/twig.php @@ -1,17 +1,16 @@ extension('twig', [ - 'file_name_pattern' => '*.twig', - 'globals' => [ - 'favicon' => '@App\Service\Favicon', - ], - ]); +return static function ( + ContainerConfigurator $containerConfigurator, + TwigConfig $twig, + ): void { if ($containerConfigurator->env() === 'test') { - $containerConfigurator->extension('twig', [ - 'strict_variables' => true, - ]); + $twig->strictVariables(true); } + $twig->formThemes(['bootstrap_5_layout.html.twig']); + $twig->fileNamePattern('*.twig'); + $twig->global('favicon', '@App\Service\Favicon'); }; diff --git a/templates/_form.html.twig b/templates/_form.html.twig index bf20b98..e0ed7ee 100644 --- a/templates/_form.html.twig +++ b/templates/_form.html.twig @@ -1,4 +1,4 @@ -{{ form_start(form) }} - {{ form_widget(form) }} - +{{ form_start(form, {'attr': {'class': 'mb-3'}}) }} + {{ form_widget(form, {'attr': {'class': 'form-control'}}) }} + {{ form_end(form) }} diff --git a/templates/base.html.twig b/templates/base.html.twig index 942c876..6e78f0a 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -11,19 +11,53 @@ {% endblock %} -
-

Hello {{ app.request.cookies.get('username', 'nobody') }} - change name

-