diff --git a/.idea/Wishlist-application.iml b/.idea/Wishlist-application.iml index f4221e4aa8438cba0d6d0051d33ae0dedc98f941..fc468d7d2cdaf5ee4ef3a72f5477cce203f2a472 100644 --- a/.idea/Wishlist-application.iml +++ b/.idea/Wishlist-application.iml @@ -20,6 +20,7 @@ <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/persistence" /> <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/sql-formatter" /> <excludeFolder url="file://$MODULE_DIR$/vendor/egulias/email-validator" /> + <excludeFolder url="file://$MODULE_DIR$/vendor/jms/metadata" /> <excludeFolder url="file://$MODULE_DIR$/vendor/masterminds/html5" /> <excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" /> <excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" /> @@ -133,6 +134,7 @@ <excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/twig/extra-bundle" /> <excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" /> + <excludeFolder url="file://$MODULE_DIR$/vendor/vich/uploader-bundle" /> <excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" /> </content> <orderEntry type="inheritedJdk" /> diff --git a/.idea/php.xml b/.idea/php.xml index e351ae08500a025fbc3271759ff0c437fa973a66..38b033e9d74480324a70b5c0bb710eb0e09707e7 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -142,6 +142,8 @@ <path value="$PROJECT_DIR$/vendor/doctrine/migrations" /> <path value="$PROJECT_DIR$/vendor/doctrine/collections" /> <path value="$PROJECT_DIR$/vendor/doctrine/instantiator" /> + <path value="$PROJECT_DIR$/vendor/vich/uploader-bundle" /> + <path value="$PROJECT_DIR$/vendor/jms/metadata" /> </include_path> </component> <component name="PhpProjectSharedConfiguration" php_language_level="8.2" /> diff --git a/composer.json b/composer.json index be710c098861ab6f6bb5a2d296adbe70b9a8bcfe..e4be74580969f9fe5ede545fba740a770ba5cbae 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,8 @@ "symfony/web-link": "7.2.*", "symfony/yaml": "7.2.*", "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/twig": "^2.12|^3.0", + "vich/uploader-bundle": "^2.5" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index f404a87bef3d6fa7b8d72ad76da2f6a599982164..c0551765396703af12a8c1a52cce026ea8978de9 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": "eaa614d3c00b5654c4cc228dcad4b8c8", + "content-hash": "b2ee6e5d461f24017c9b451bcf06e3f1", "packages": [ { "name": "composer/semver", @@ -1368,6 +1368,70 @@ ], "time": "2024-12-27T00:36:43+00:00" }, + { + "name": "jms/metadata", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "require-dev": { + "doctrine/cache": "^1.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5|^9.0", + "psr/container": "^1.0|^2.0", + "symfony/cache": "^3.1|^4.0|^5.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "support": { + "issues": "https://github.com/schmittjoh/metadata/issues", + "source": "https://github.com/schmittjoh/metadata/tree/2.8.0" + }, + "time": "2023-02-15T13:44:18+00:00" + }, { "name": "monolog/monolog", "version": "3.8.1", @@ -7501,6 +7565,114 @@ ], "time": "2025-02-13T08:34:43+00:00" }, + { + "name": "vich/uploader-bundle", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/dustin10/VichUploaderBundle.git", + "reference": "79fd69ad8f32d4a33ac8783d7720e16e78bb8098" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/79fd69ad8f32d4a33ac8783d7720e16e78bb8098", + "reference": "79fd69ad8f32d4a33ac8783d7720e16e78bb8098", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^3.0", + "ext-simplexml": "*", + "jms/metadata": "^2.4", + "php": "^8.1", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher-contracts": "^3.1", + "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/mime": "^5.4 || ^6.0 || ^7.0", + "symfony/property-access": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "league/flysystem": "<2.0" + }, + "require-dev": { + "dg/bypass-finals": "^1.8", + "doctrine/doctrine-bundle": "^2.7", + "doctrine/mongodb-odm": "^2.4", + "doctrine/orm": "^2.13", + "ext-sqlite3": "*", + "knplabs/knp-gaufrette-bundle": "dev-master", + "league/flysystem-bundle": "^2.4 || ^3.0", + "league/flysystem-memory": "^2.0 || ^3.0", + "matthiasnoback/symfony-dependency-injection-test": "^5.1", + "mikey179/vfsstream": "^1.6.11", + "phpunit/phpunit": "^9.6", + "symfony/asset": "^5.4 || ^6.0 || ^7.0", + "symfony/browser-kit": "^5.4 || ^6.0 || ^7.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/dom-crawler": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^7.0", + "symfony/security-csrf": "^5.4 || ^6.0 || ^7.0", + "symfony/translation": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "For integration with Doctrine", + "doctrine/mongodb-odm-bundle": "For integration with Doctrine ODM", + "doctrine/orm": "For integration with Doctrine ORM", + "doctrine/phpcr-odm": "For integration with Doctrine PHPCR", + "knplabs/knp-gaufrette-bundle": "For integration with Gaufrette", + "league/flysystem-bundle": "For integration with Flysystem", + "liip/imagine-bundle": "To generate image thumbnails", + "oneup/flysystem-bundle": "For integration with Flysystem", + "symfony/asset": "To generate better links", + "symfony/form": "To handle uploads in forms", + "symfony/yaml": "To use YAML mapping" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Vich\\UploaderBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dustin Dobervich", + "email": "ddobervich@gmail.com" + } + ], + "description": "Ease file uploads attached to entities", + "homepage": "https://github.com/dustin10/VichUploaderBundle", + "keywords": [ + "file uploads", + "upload" + ], + "support": { + "issues": "https://github.com/dustin10/VichUploaderBundle/issues", + "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.5.1" + }, + "time": "2024-12-31T08:59:03+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/bundles.php b/config/bundles.php index 4e3a56077fb806de5799fe7e82171145e3ce4ff5..0dcb24b223b0bb7faebe09146bb9dcece4b5f2ad 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -12,5 +12,6 @@ return [ Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], ]; diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1a636c10eaa7167374e8306f92c3bd4ba6cd0c2a --- /dev/null +++ b/config/packages/vich_uploader.yaml @@ -0,0 +1,7 @@ +vich_uploader: + db_driver: orm + mappings: + item_images: + uri_prefix: /uploads/images + upload_destination: '%kernel.project_dir%/public/uploads/images' + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer \ No newline at end of file diff --git a/migrations/.gitignore b/migrations/.gitignore deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/migrations/Version20250319143938.php b/migrations/Version20250319154617.php similarity index 87% rename from migrations/Version20250319143938.php rename to migrations/Version20250319154617.php index 5a0fa86f2d70d55a630c67b24eb57ec7a4e6cf42..923e42fd5420faf7c6a781e383207fb1ee361499 100644 --- a/migrations/Version20250319143938.php +++ b/migrations/Version20250319154617.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20250319143938 extends AbstractMigration +final class Version20250319154617 extends AbstractMigration { public function getDescription(): string { @@ -20,7 +20,7 @@ final class Version20250319143938 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, purchase_proof_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) NOT NULL, price DOUBLE PRECISION NOT NULL, UNIQUE INDEX UNIQ_1F1B251EFECA7547 (purchase_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, purchase_proof_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, UNIQUE INDEX UNIQ_1F1B251EFECA7547 (purchase_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..dfb0f6041c52d81d79290a54c8a255f5a20bbef3 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,100 @@ +* { + box-sizing: border-box; + font-family: Helvetica, Arial, sans-serif; +} + +body { + margin: 0; + background: #f4f4f4; +} + +header { + display: flex; + align-items: center; + background: #00B8DE; + color: white; + padding: 10px; + gap: 15px; +} + +.user-icon { + width: 35px; + height: 35px; + background: #ccc; + border-radius: 50%; +} + +h1 { + margin: 0; +} + +h1 a { + color: white; + text-decoration: none; +} + +.search-bar { + margin-left: auto; + padding: 5px; + border-radius: 5px; + border: none; +} + +.container { + display: flex; + justify-content: space-around; + align-items: flex-start; + margin: 20px; +} + +.info-text { + color: red; + font-size: 14px; + max-width: 150px; +} + +.form-section { + background: white; + padding: 20px; + border: 2px solid #99CC33; + border-radius: 10px; + width: 300px; +} + +.input-box label { + display: block; + font-weight: bold; + margin-top: 10px; +} + +.input-box input, +.input-box textarea { + width: 100%; + padding: 8px; + border: 1px solid #00B8DE; + border-radius: 5px; +} + +.image-section { + background: white; + padding: 20px; + border: 2px solid #99CC33; + border-radius: 10px; + text-align: center; +} + +.image-label { + font-weight: bold; + display: block; + margin-bottom: 10px; +} + +.image-upload-box { + border: 2px dashed #00B8DE; + padding: 40px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; +} + diff --git a/public/uploads/images/image-67daec93d2de5553979377.jpg b/public/uploads/images/image-67daec93d2de5553979377.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85b79202e906b754db44422ca371be61a633867b Binary files /dev/null and b/public/uploads/images/image-67daec93d2de5553979377.jpg differ diff --git a/public/uploads/images/image-67daee56bb05a205705852.jpg b/public/uploads/images/image-67daee56bb05a205705852.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85b79202e906b754db44422ca371be61a633867b Binary files /dev/null and b/public/uploads/images/image-67daee56bb05a205705852.jpg differ diff --git a/public/uploads/images/image-67db4b30ea363264558781.jpg b/public/uploads/images/image-67db4b30ea363264558781.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85b79202e906b754db44422ca371be61a633867b Binary files /dev/null and b/public/uploads/images/image-67db4b30ea363264558781.jpg differ diff --git a/src/Controller/ItemController.php b/src/Controller/ItemController.php new file mode 100644 index 0000000000000000000000000000000000000000..365fbc0baee599846533aec362f6f07868e7aa69 --- /dev/null +++ b/src/Controller/ItemController.php @@ -0,0 +1,81 @@ +<?php + +namespace App\Controller; + +use App\Entity\Item; +use App\Form\ItemType; +use App\Repository\ItemRepository; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +#[Route('/item')] +final class ItemController extends AbstractController +{ + #[Route(name: 'app_item_index', methods: ['GET'])] + public function index(ItemRepository $itemRepository): Response + { + return $this->render('item/index.html.twig', [ + 'items' => $itemRepository->findAll(), + ]); + } + + #[Route('/new', name: 'app_item_new', methods: ['GET', 'POST'])] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $item = new Item(); + $form = $this->createForm(ItemType::class, $item); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($item); + $entityManager->flush(); + + return $this->redirectToRoute('app_item_index'); + } + + return $this->render('item/new.html.twig', [ + 'item' => $item, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_item_show', methods: ['GET'])] + public function show(Item $item): Response + { + return $this->render('item/show.html.twig', [ + 'item' => $item, + ]); + } + + #[Route('/{id}/edit', name: 'app_item_edit', methods: ['GET', 'POST'])] + public function edit(Request $request, Item $item, EntityManagerInterface $entityManager): Response + { + $form = $this->createForm(ItemType::class, $item); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('app_item_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('item/edit.html.twig', [ + 'item' => $item, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_item_delete', methods: ['POST'])] + public function delete(Request $request, Item $item, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$item->getId(), $request->getPayload()->getString('_token'))) { + $entityManager->remove($item); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_item_index', [], Response::HTTP_SEE_OTHER); + } +} diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 2db4fc660557e63c4b2b6627d9280208d1a62ed4..99263f3324512810c625789913586b9bacf24b3f 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -4,8 +4,12 @@ namespace App\Entity; use App\Repository\ItemRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\Validator\Constraints as Assert; +use Vich\UploaderBundle\Mapping\Annotation as Vich; #[ORM\Entity(repositoryClass: ItemRepository::class)] +#[Vich\Uploadable] class Item { #[ORM\Id] @@ -22,9 +26,16 @@ class Item #[ORM\Column(length: 255)] private ?string $url = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255, nullable: true)] private ?string $image = null; + #[Vich\UploadableField(mapping: 'item_images', fileNameProperty: 'image')] + #[Assert\Image( + mimeTypes: ["image/jpeg", "image/png"], + maxSize: "5M" + )] + private ?File $imageFile = null; + #[ORM\Column] private ?float $price = null; @@ -44,7 +55,6 @@ class Item public function setTitle(string $title): static { $this->title = $title; - return $this; } @@ -56,7 +66,6 @@ class Item public function setDescription(string $description): static { $this->description = $description; - return $this; } @@ -68,7 +77,6 @@ class Item public function setUrl(string $url): static { $this->url = $url; - return $this; } @@ -77,13 +85,22 @@ class Item return $this->image; } - public function setImage(string $image): static + public function setImage(?string $image): self { $this->image = $image; - return $this; } + public function getImageFile(): ?File + { + return $this->imageFile; + } + + public function setImageFile(?File $imageFile): void + { + $this->imageFile = $imageFile; + } + public function getPrice(): ?float { return $this->price; @@ -92,7 +109,6 @@ class Item public function setPrice(float $price): static { $this->price = $price; - return $this; } @@ -104,7 +120,6 @@ class Item public function setPurchaseProof(?PurchaseProof $purchaseProof): static { $this->purchaseProof = $purchaseProof; - return $this; } } diff --git a/src/Form/ItemType.php b/src/Form/ItemType.php new file mode 100644 index 0000000000000000000000000000000000000000..a54aba6180dd3f6bf3a09aed6a85a28295d9a0ea --- /dev/null +++ b/src/Form/ItemType.php @@ -0,0 +1,40 @@ +<?php + +namespace App\Form; + +use App\Entity\Item; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Vich\UploaderBundle\Form\Type\VichImageType; + +class ItemType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('title', TextType::class, ['label' => 'Article name']) + ->add('url', TextType::class, ['label' => 'Buy URL']) + ->add('price', NumberType::class, ['label' => 'Price']) + ->add('description', TextType::class, ['label' => 'Description']) + ->add('imageFile', VichImageType::class, [ + 'label' => 'Upload a new image', // ✅ Change le label pour éviter la confusion + 'required' => false, // ✅ Permet de ne pas forcer un nouvel upload + 'allow_delete' => false, // ✅ Désactive la suppression automatique + 'download_uri' => false, // ✅ Désactive le lien de téléchargement + 'image_uri' => false, // ✅ Empêche l'affichage de l'image dans l'input + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Item::class, + 'csrf_protection' => true, // ✅ Active la protection CSRF + 'csrf_field_name' => '_token', // ✅ Définit le nom du champ CSRF + 'csrf_token_id' => 'submit', // ✅ Identifie le token + ]); + } +} diff --git a/symfony.lock b/symfony.lock index 494742435e71508edaa6b77a2067a9728d7cd36e..2101fd274bcf20f927f2d6a13578218b0a1d6f5e 100644 --- a/symfony.lock +++ b/symfony.lock @@ -298,5 +298,14 @@ }, "twig/extra-bundle": { "version": "v3.20.0" + }, + "vich/uploader-bundle": { + "version": "2.5", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.13", + "ref": "1b3064c2f6b255c2bc2f56461aaeb76b11e07e36" + } } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 3cda30fb023bd2dcf148bf08b8658ec8ba1a332a..2eac7d55cf86cc2a94f66f2aaddedac60af3edff 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -4,6 +4,7 @@ <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>"> + <link rel="stylesheet" href="{{ asset('css/style.css') }}"> {% block stylesheets %} {% endblock %} diff --git a/templates/item/_delete_form.html.twig b/templates/item/_delete_form.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..5793a05562d579ec720fbf5476046f5f38894409 --- /dev/null +++ b/templates/item/_delete_form.html.twig @@ -0,0 +1,4 @@ +<form method="post" action="{{ path('app_item_delete', {'id': item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');"> + <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ item.id) }}"> + <button class="btn">Delete</button> +</form> diff --git a/templates/item/_form.html.twig b/templates/item/_form.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..bf20b98fb01ed38c82b670ff3fe5d7e207e80f16 --- /dev/null +++ b/templates/item/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + <button class="btn">{{ button_label|default('Save') }}</button> +{{ form_end(form) }} diff --git a/templates/item/edit.html.twig b/templates/item/edit.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..e10931fa28225b74a7a071d459edf64ad649b118 --- /dev/null +++ b/templates/item/edit.html.twig @@ -0,0 +1,44 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ item is defined ? 'Edit Item' : 'New Item' }}{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="{{ path('app_item_index') }}">Item title</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + {{ form_start(form, {'attr': {'class': 'input-box'}}) }} + + {{ form_row(form.title) }} + {{ form_row(form.url) }} + {{ form_row(form.price) }} + {{ form_row(form.description) }} + + <!-- ✅ Affichage unique de l'image actuelle --> + {% if item is defined and item.image %} + <h3>Current Image</h3> + <div class="image-container"> + <img src="{{ asset('uploads/images/' ~ item.image) }}" + alt="Item Image" + style="max-width: 200px; border-radius: 5px; display: block; margin-bottom: 10px;"> + </div> + {% endif %} + + <!-- ✅ Champ d'upload sans affichage d'image --> + <h3>Upload a new image</h3> + <label for="image-upload" class="upload-label">Choose a file</label> + {{ form_widget(form.imageFile, {'attr': {'class': 'file-input', 'id': 'image-upload'}}) }} + + <button class="btn">{{ item is defined ? 'Update' : 'Confirm' }}</button> + + {# ✅ Empêche Symfony d'afficher les champs restants automatiquement #} + {{ form_end(form) }} + </section> + </div> + </main> +{% endblock %} diff --git a/templates/item/index.html.twig b/templates/item/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..ea9218712fb00085a6958ab9bb79fc155c247748 --- /dev/null +++ b/templates/item/index.html.twig @@ -0,0 +1,57 @@ +{% extends 'base.html.twig' %} + +{% block title %}Item index{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="#">Item Management</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + <h1>Item index</h1> + + <table class="table"> + <thead> + <tr> + <th>Id</th> + <th>Title</th> + <th>Description</th> + <th>Url</th> + <th>Image</th> + <th>Price</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for item in items %} + <tr> + <td>{{ item.id }}</td> + <td>{{ item.title }}</td> + <td>{{ item.description }}</td> + <td>{{ item.url }}</td> + <td>{{ item.image }}</td> + <td>{{ item.price }}</td> + <td> + <a href="{{ path('app_item_show', {'id': item.id}) }}">Show</a> + <a href="{{ path('app_item_edit', {'id': item.id}) }}">Edit</a> + </td> + </tr> + {% else %} + <tr> + <td colspan="7">No records found</td> + </tr> + {% endfor %} + </tbody> + </table> + + <a href="{{ path('app_item_new') }}" class="btn">Create new</a> + </section> + </div> + </main> + + +{% endblock %} diff --git a/templates/item/new.html.twig b/templates/item/new.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..9773d03919bc8ece5a13a2a4c4505890726c226e --- /dev/null +++ b/templates/item/new.html.twig @@ -0,0 +1,30 @@ +{% extends 'base.html.twig' %} + +{% block title %}New Item{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="{{ path('app_item_index') }}">Item title</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + {{ form_start(form, {'attr': {'class': 'input-box'}}) }} + {{ form_row(form.title) }} + {{ form_row(form.url) }} + {{ form_row(form.price) }} + {{ form_row(form.description) }} + + <!-- Upload d’image --> + <h3>Item image</h3> + {{ form_row(form.imageFile) }} + + <button class="btn">Confirm</button> + {{ form_end(form) }} + </section> + </div> + </main> +{% endblock %} diff --git a/templates/item/show.html.twig b/templates/item/show.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..e7c5f66933b3779c025fe9f91a0b324b0cd5d73d --- /dev/null +++ b/templates/item/show.html.twig @@ -0,0 +1,42 @@ +{% extends 'base.html.twig' %} + +{% block title %}Item{% endblock %} + +{% block body %} + <h1>Item</h1> + + <table class="table"> + <tbody> + <tr> + <th>Id</th> + <td>{{ item.id }}</td> + </tr> + <tr> + <th>Title</th> + <td>{{ item.title }}</td> + </tr> + <tr> + <th>Description</th> + <td>{{ item.description }}</td> + </tr> + <tr> + <th>Url</th> + <td>{{ item.url }}</td> + </tr> + <tr> + <th>Image</th> + <td>{{ item.image }}</td> + </tr> + <tr> + <th>Price</th> + <td>{{ item.price }}</td> + </tr> + </tbody> + </table> + + <a href="{{ path('app_item_index') }}">back to list</a> + + <a href="{{ path('app_item_edit', {'id': item.id}) }}">edit</a> + + {{ include('item/_delete_form.html.twig') }} +{% endblock %}