Compare commits
24 Commits
52933658cf
...
main
Author | SHA1 | Date | |
---|---|---|---|
b0bd392ce3 | |||
51c3be30db | |||
c93bfd585e | |||
0524212df6 | |||
26950905d6 | |||
4a84f5ec7d | |||
f6f7edcf34 | |||
2eaaa1d049 | |||
1b96974bcb | |||
4acc1955a9 | |||
60e6d9daf7 | |||
4533246181 | |||
a8e2510a03 | |||
31f64b9e5a | |||
ed268e96fa | |||
5ad47bb626 | |||
2e3783399e | |||
4d541a860f | |||
da74ab2ea4 | |||
c20f3bc933 | |||
74288b17a9 | |||
271c7c67e9 | |||
ced8d321bc | |||
ae09474b49 |
@ -2,7 +2,7 @@ APP_ENV=dev
|
|||||||
APP_SECRET=
|
APP_SECRET=
|
||||||
|
|
||||||
DB_ROOT_PASSWORD=rootpswd
|
DB_ROOT_PASSWORD=rootpswd
|
||||||
DB_DATABASE=cloudsprint
|
DB_DATABASE=booknest
|
||||||
DB_USERNAME=dev
|
DB_USERNAME=dev
|
||||||
DB_PASSWORD=cfai42
|
DB_PASSWORD=cfai42
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
|
28
assets/js/modal.js
Normal file
28
assets/js/modal.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const datas = {{ datas | tojson }};
|
||||||
|
|
||||||
|
datas.items.forEach((book, index) => {
|
||||||
|
const openModalBtn = document.getElementById(`openModalBtn-${index + 1}`);
|
||||||
|
const closeModalSvg = document.getElementById(`closeModalSvg-${index + 1}`);
|
||||||
|
const closeModalBtn = document.getElementById(`closeModalBtn-${index + 1}`);
|
||||||
|
const modal = document.getElementById(`myModal-${index + 1}`);
|
||||||
|
|
||||||
|
if (openModalBtn) {
|
||||||
|
openModalBtn.addEventListener('click', () => {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeModalSvg) {
|
||||||
|
closeModalSvg.addEventListener('click', () => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeModalBtn) {
|
||||||
|
closeModalBtn.addEventListener('click', () => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -19,6 +19,7 @@
|
|||||||
"symfony/runtime": "7.0.*",
|
"symfony/runtime": "7.0.*",
|
||||||
"symfony/security-bundle": "7.0.*",
|
"symfony/security-bundle": "7.0.*",
|
||||||
"symfony/twig-bundle": "7.0.*",
|
"symfony/twig-bundle": "7.0.*",
|
||||||
|
"symfony/validator": "7.0.*",
|
||||||
"symfony/webpack-encore-bundle": "^2.2",
|
"symfony/webpack-encore-bundle": "^2.2",
|
||||||
"symfony/yaml": "7.0.*",
|
"symfony/yaml": "7.0.*",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
|
97
composer.lock
generated
97
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "c53910010d80c77569e2b6c0828c9aa2",
|
"content-hash": "c7a74db8c19635e6115f7567b0077bb0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "doctrine/cache",
|
"name": "doctrine/cache",
|
||||||
@ -5374,6 +5374,101 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-11-26T15:16:53+00:00"
|
"time": "2023-11-26T15:16:53+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/validator",
|
||||||
|
"version": "v7.0.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/validator.git",
|
||||||
|
"reference": "b3e4d838cdae9f2882402c2ad8018a27d469c075"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/validator/zipball/b3e4d838cdae9f2882402c2ad8018a27d469c075",
|
||||||
|
"reference": "b3e4d838cdae9f2882402c2ad8018a27d469c075",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
|
"symfony/polyfill-mbstring": "~1.0",
|
||||||
|
"symfony/polyfill-php83": "^1.27",
|
||||||
|
"symfony/translation-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/lexer": "<1.1",
|
||||||
|
"symfony/dependency-injection": "<6.4",
|
||||||
|
"symfony/doctrine-bridge": "<7.0",
|
||||||
|
"symfony/expression-language": "<6.4",
|
||||||
|
"symfony/http-kernel": "<6.4",
|
||||||
|
"symfony/intl": "<6.4",
|
||||||
|
"symfony/property-info": "<6.4",
|
||||||
|
"symfony/translation": "<6.4.3|>=7.0,<7.0.3",
|
||||||
|
"symfony/yaml": "<6.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"egulias/email-validator": "^2.1.10|^3|^4",
|
||||||
|
"symfony/cache": "^6.4|^7.0",
|
||||||
|
"symfony/config": "^6.4|^7.0",
|
||||||
|
"symfony/console": "^6.4|^7.0",
|
||||||
|
"symfony/dependency-injection": "^6.4|^7.0",
|
||||||
|
"symfony/expression-language": "^6.4|^7.0",
|
||||||
|
"symfony/finder": "^6.4|^7.0",
|
||||||
|
"symfony/http-client": "^6.4|^7.0",
|
||||||
|
"symfony/http-foundation": "^6.4|^7.0",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0",
|
||||||
|
"symfony/intl": "^6.4|^7.0",
|
||||||
|
"symfony/mime": "^6.4|^7.0",
|
||||||
|
"symfony/property-access": "^6.4|^7.0",
|
||||||
|
"symfony/property-info": "^6.4|^7.0",
|
||||||
|
"symfony/translation": "^6.4.3|^7.0.3",
|
||||||
|
"symfony/yaml": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Validator\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/",
|
||||||
|
"/Resources/bin/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides tools to validate values",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/validator/tree/v7.0.10"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-07-26T12:31:22+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/var-dumper",
|
"name": "symfony/var-dumper",
|
||||||
"version": "v7.0.2",
|
"version": "v7.0.2",
|
||||||
|
@ -7,9 +7,11 @@ framework:
|
|||||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||||
# Remove or comment this section to explicitly disable session support.
|
# Remove or comment this section to explicitly disable session support.
|
||||||
session:
|
session:
|
||||||
handler_id: null
|
enabled: true
|
||||||
cookie_secure: auto
|
handler_id: null # Utilise le gestionnaire de sessions par défaut
|
||||||
cookie_samesite: lax
|
cookie_lifetime: 3600 # La durée de vie des cookies de session
|
||||||
|
cookie_secure: auto # Assure-toi que les cookies sont sécurisés (seulement en HTTPS)
|
||||||
|
cookie_samesite: lax # La politique SameSite des cookies
|
||||||
|
|
||||||
#esi: true
|
#esi: true
|
||||||
#fragments: true
|
#fragments: true
|
||||||
|
@ -10,23 +10,18 @@ security:
|
|||||||
class: App\Entity\User
|
class: App\Entity\User
|
||||||
property: email
|
property: email
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
|
||||||
security: false
|
|
||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
|
form_login:
|
||||||
# activate different ways to authenticate
|
login_path: app_login
|
||||||
# https://symfony.com/doc/current/security.html#the-firewall
|
check_path: app_login
|
||||||
|
username_parameter: _username
|
||||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
password_parameter: _password
|
||||||
# switch_user: true
|
default_target_path: home # Assure-toi que cette route existe
|
||||||
|
|
||||||
# Ajout de la gestion de la déconnexion
|
|
||||||
logout:
|
logout:
|
||||||
path: /logout # Route pour déconnecter l'utilisateur
|
path: /logout
|
||||||
target: /login # Redirection vers la page de login après déconnexion
|
target: /login
|
||||||
|
|
||||||
# Easy way to control access for large sections of your site
|
# Easy way to control access for large sections of your site
|
||||||
# Note: Only the *first* access control that matches will be used
|
# Note: Only the *first* access control that matches will be used
|
||||||
|
@ -7,6 +7,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use App\Repository\FavorisRepository;
|
||||||
|
use App\Entity\Favoris;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
class APISearchController extends AbstractController
|
class APISearchController extends AbstractController
|
||||||
{
|
{
|
||||||
@ -21,21 +24,80 @@ class APISearchController extends AbstractController
|
|||||||
{
|
{
|
||||||
// Récupérer le paramètre "q" depuis la requête
|
// Récupérer le paramètre "q" depuis la requête
|
||||||
$query = $request->query->get('q');
|
$query = $request->query->get('q');
|
||||||
|
$nb_pages = $request->query->get('page') ?? '1';
|
||||||
|
|
||||||
|
|
||||||
// Appeler le service GoogleBooks avec la requête
|
// Appeler le service GoogleBooks avec la requête
|
||||||
return $this->googleBooksService->searchBooks($query);
|
return $this->googleBooksService->searchBooks($query, 'fr', ($nb_pages - 1) *10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/toggleLike/{idGoogle}', name: 'like', methods: "POST")]
|
||||||
|
public function addFavoris(Request $request, FavorisRepository $favorisRepository, String $idGoogle)
|
||||||
|
{
|
||||||
|
$content = $request->getContent();
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE || empty($data)) {
|
||||||
|
return new JsonResponse(['status' => 'error', 'message' => 'Invalid data'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = $data['title'] ?? '';
|
||||||
|
$authors = $data['authors'] ?? '';
|
||||||
|
$images = $data['images'] ?? '';
|
||||||
|
$description = $data['description'] ?? '';
|
||||||
|
$date = $data['date'] ?? '';
|
||||||
|
$pages = $data['pages'] ?? '';
|
||||||
|
$edition = $data['edition'] ?? '';
|
||||||
|
$categorie = $data['categorie'] ?? '';
|
||||||
|
|
||||||
|
|
||||||
|
$favoris = new Favoris();
|
||||||
|
$user = $this->getUser();
|
||||||
|
$favoris->setUser($user);
|
||||||
|
$favoris->setIdGoogle($idGoogle);
|
||||||
|
$favoris->setTitle($title);
|
||||||
|
$favoris->setAuthors($authors);
|
||||||
|
$favoris->setImage($images);
|
||||||
|
$favoris->setDescription($description);
|
||||||
|
$favoris->setPublication($date);
|
||||||
|
$favoris->setPages($pages);
|
||||||
|
$favoris->setPublication($date);
|
||||||
|
$favoris->setEdition($edition);
|
||||||
|
$favoris->setCategorie($categorie);
|
||||||
|
|
||||||
|
|
||||||
|
$favorisRepository->addFavoris($favoris);
|
||||||
|
return $this->json(['success' => true, 'message' => 'Favoris ajouté', "status" => "added"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/untoggleLike/{idGoogle}', name: 'unlike', methods: "POST")]
|
||||||
|
public function removeFavoris(FavorisRepository $favorisRepository, String $idGoogle)
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
$favorisRepository->removeFavoris($user, $idGoogle);
|
||||||
|
|
||||||
|
return $this->json(['success' => true, 'message' => 'Favoris supprimé', "status" => "removed"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/search', name: 'api_search')]
|
#[Route('/api/search', name: 'api_search')]
|
||||||
public function index(Request $request): Response
|
public function index(Request $request, FavorisRepository $favorisRepository): Response
|
||||||
{
|
{
|
||||||
|
ini_set('memory_limit', '512M');
|
||||||
|
|
||||||
// Appeler la méthode search et récupérer les résultats
|
// Appeler la méthode search et récupérer les résultats
|
||||||
$datas = $this->search($request);
|
$datas = $this->search($request);
|
||||||
|
$query = $request->query->get('q');
|
||||||
|
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
$favoris = $favorisRepository->getFavorisByUser($user);
|
||||||
|
|
||||||
// Afficher les résultats dans le template
|
// Afficher les résultats dans le template
|
||||||
return $this->render('apiSearch/index.html.twig', [
|
return $this->render('apiSearch/index.html.twig', [
|
||||||
'controller_name' => 'APISearchController',
|
'controller_name' => 'APISearchController',
|
||||||
'datas' => $datas,
|
'datas' => $datas,
|
||||||
|
'query' => $query,
|
||||||
|
'favoris' => $favoris,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
src/Controller/FavorisController.php
Normal file
33
src/Controller/FavorisController.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\FavorisRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use App\Service\GoogleBooksService;
|
||||||
|
|
||||||
|
class FavorisController extends AbstractController
|
||||||
|
{
|
||||||
|
private GoogleBooksService $googleBooksService;
|
||||||
|
private array $favorisResult = [];
|
||||||
|
|
||||||
|
public function __construct(GoogleBooksService $googleBooksService)
|
||||||
|
{
|
||||||
|
$this->googleBooksService = $googleBooksService;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/favoris', name: 'app_favoris')]
|
||||||
|
public function index(FavorisRepository $favorisRepository): Response
|
||||||
|
{
|
||||||
|
$favorisUser = $favorisRepository->findBy(['user' => $this->getUser()]);
|
||||||
|
ini_set('memory_limit', '512M');
|
||||||
|
array_push($this->favorisResult, $favorisRepository->getFavorisByUser($this->getUser()));
|
||||||
|
return $this->render('favoris/index.html.twig', [
|
||||||
|
'controller_name' => 'FavorisController',
|
||||||
|
'datas' => $this->favorisResult,
|
||||||
|
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
@ -10,16 +9,19 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
class RegistrationController extends AbstractController
|
class RegistrationController extends AbstractController
|
||||||
{
|
{
|
||||||
private $entityManager;
|
private $entityManager;
|
||||||
private $passwordHasher; // Correction du nom de la variable
|
private $passwordHasher;
|
||||||
|
private $validator;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher) // Correction ici
|
public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher, ValidatorInterface $validator)
|
||||||
{
|
{
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
$this->passwordHasher = $passwordHasher;
|
$this->passwordHasher = $passwordHasher;
|
||||||
|
$this->validator = $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/registration', name: 'app_registration')]
|
#[Route('/registration', name: 'app_registration')]
|
||||||
@ -29,19 +31,43 @@ class RegistrationController extends AbstractController
|
|||||||
$form = $this->createForm(RegistrationType::class, $user);
|
$form = $this->createForm(RegistrationType::class, $user);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted()) {
|
||||||
// Hacher le mot de passe avant de persister l'utilisateur
|
$existingEmail = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $user->getEmail()]);
|
||||||
$plainPassword = $user->getPassword(); // Récupération du mot de passe brut
|
$existingPseudo = $this->entityManager->getRepository(User::class)->findOneBy(['pseudo' => $user->getPseudo()]);
|
||||||
$hashedPassword = $this->passwordHasher->hashPassword($user, $plainPassword); // Hachage du mot de passe
|
|
||||||
$user->setPassword($hashedPassword); // Remplacer le mot de passe brut par le mot de passe haché
|
|
||||||
|
|
||||||
// Persist de l'utilisateur haché
|
if ($existingEmail) {
|
||||||
$this->entityManager->persist($user);
|
$form->get('email')->addError(new \Symfony\Component\Form\FormError('Cet email est déjà utilisé.'));
|
||||||
$this->entityManager->flush();
|
}
|
||||||
|
|
||||||
// Message flash de succès
|
if ($existingPseudo) {
|
||||||
$this->addFlash('success', 'Votre compte a été créé avec succès !');
|
$form->get('pseudo')->addError(new \Symfony\Component\Form\FormError('Ce pseudo est déjà pris.'));
|
||||||
return $this->redirectToRoute('home');
|
}
|
||||||
|
|
||||||
|
$plainPassword = $user->getPassword();
|
||||||
|
$confirmPassword = $form->get('confirmPassword')->getData();
|
||||||
|
|
||||||
|
if ($plainPassword !== $confirmPassword) {
|
||||||
|
$this->addFlash('error', 'Les mots de passe ne correspondent pas.');
|
||||||
|
return $this->redirectToRoute('app_registration');
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = $this->validator->validate($user);
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
$form->addError(new \Symfony\Component\Form\FormError($error->getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$hashedPassword = $this->passwordHasher->hashPassword($user, $plainPassword);
|
||||||
|
$user->setPassword($hashedPassword);
|
||||||
|
|
||||||
|
$this->entityManager->persist($user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Votre compte a été créé avec succès !');
|
||||||
|
return $this->redirectToRoute('home');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('registration/index.html.twig', [
|
return $this->render('registration/index.html.twig', [
|
||||||
|
170
src/Entity/Favoris.php
Normal file
170
src/Entity/Favoris.php
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\FavorisRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: FavorisRepository::class)]
|
||||||
|
class Favoris
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $id_google = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'favoris')]
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $title = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $authors = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $edition = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $publication = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $categorie = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $pages = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 2555, nullable: true)]
|
||||||
|
private ?string $description = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $image = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIdGoogle(): ?string
|
||||||
|
{
|
||||||
|
return $this->id_google;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIdGoogle(string $id_google): static
|
||||||
|
{
|
||||||
|
$this->id_google = $id_google;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(?User $user): static
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): ?string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(string $title): static
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthors(): ?string
|
||||||
|
{
|
||||||
|
return $this->authors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAuthors(?string $authors): static
|
||||||
|
{
|
||||||
|
$this->authors = $authors;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEdition(): ?string
|
||||||
|
{
|
||||||
|
return $this->edition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEdition(?string $edition): static
|
||||||
|
{
|
||||||
|
$this->edition = $edition;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPublication(): ?string
|
||||||
|
{
|
||||||
|
return $this->publication;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPublication(?string $publication): static
|
||||||
|
{
|
||||||
|
$this->publication = $publication;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategorie(): ?string
|
||||||
|
{
|
||||||
|
return $this->categorie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCategorie(?string $categorie): static
|
||||||
|
{
|
||||||
|
$this->categorie = $categorie;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPages(): ?string
|
||||||
|
{
|
||||||
|
return $this->pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPages(?string $pages): static
|
||||||
|
{
|
||||||
|
$this->pages = $pages;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): ?string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDescription(string $description): static
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImage(): ?string
|
||||||
|
{
|
||||||
|
return $this->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setImage(?string $image): static
|
||||||
|
{
|
||||||
|
$this->image = $image;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
@ -31,6 +33,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $lastName = null;
|
private ?string $lastName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: "json")]
|
||||||
|
private array $roles = [];
|
||||||
|
|
||||||
|
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Favoris::class)]
|
||||||
|
private Collection $favoris;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->favoris = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
@ -103,16 +117,56 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
|
|
||||||
public function getRoles(): array
|
public function getRoles(): array
|
||||||
{
|
{
|
||||||
// TODO: Implement getRoles() method.
|
|
||||||
|
$roles = $this->roles;
|
||||||
|
|
||||||
|
if (empty($roles)) {
|
||||||
|
$roles[] = 'ROLE_USER';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $roles;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function eraseCredentials(): void
|
public function eraseCredentials(): void
|
||||||
{
|
{
|
||||||
// TODO: Implement eraseCredentials() method.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserIdentifier(): string
|
public function getUserIdentifier(): string
|
||||||
{
|
{
|
||||||
// TODO: Implement getUserIdentifier() method.
|
|
||||||
|
return $this->email; // Ou $this->pseudo si tu préfères utiliser le pseudo
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Favoris>
|
||||||
|
*/
|
||||||
|
public function getFavoris(): Collection
|
||||||
|
{
|
||||||
|
return $this->favoris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFavori(Favoris $favori): static
|
||||||
|
{
|
||||||
|
if (!$this->favoris->contains($favori)) {
|
||||||
|
$this->favoris->add($favori);
|
||||||
|
$favori->setUser($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFavori(Favoris $favori): static
|
||||||
|
{
|
||||||
|
if ($this->favoris->removeElement($favori)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($favori->getUser() === $this) {
|
||||||
|
$favori->setUser(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,39 +12,44 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
|
|
||||||
class RegistrationType extends AbstractType
|
class RegistrationType extends AbstractType
|
||||||
{
|
{
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('email', EmailType::class, [
|
->add('email', EmailType::class, [
|
||||||
'label' => 'Email',
|
'label' => 'Email',
|
||||||
'attr' => ['class' => 'form-control']
|
'attr' => ['class' => 'form-control']
|
||||||
])
|
])
|
||||||
->add('password', PasswordType::class, [
|
->add('password', PasswordType::class, [
|
||||||
'label' => 'Mot de passe',
|
'label' => 'Mot de passe',
|
||||||
'attr' => ['class' => 'form-control']
|
'attr' => ['class' => 'form-control']
|
||||||
])
|
])
|
||||||
->add('pseudo', TextType::class, [
|
->add('confirmPassword', PasswordType::class, [
|
||||||
'label' => 'Pseudo',
|
'label' => 'Confirmer le mot de passe',
|
||||||
'attr' => ['class' => 'form-control']
|
'attr' => ['class' => 'form-control'],
|
||||||
])
|
'mapped' => false, // Ce champ n'est pas mappé à l'entité User
|
||||||
->add('firstname', TextType::class, [
|
])
|
||||||
'label' => 'Prénom',
|
->add('pseudo', TextType::class, [
|
||||||
'attr' => ['class' => 'form-control']
|
'label' => 'Pseudo',
|
||||||
])
|
'attr' => ['class' => 'form-control']
|
||||||
->add('lastname', TextType::class, [
|
])
|
||||||
'label' => 'Nom',
|
->add('firstname', TextType::class, [
|
||||||
'attr' => ['class' => 'form-control']
|
'label' => 'Prénom',
|
||||||
])
|
'attr' => ['class' => 'form-control']
|
||||||
->add('submit', SubmitType::class, [
|
])
|
||||||
'label' => 'S\'inscrire',
|
->add('lastname', TextType::class, [
|
||||||
'attr' => ['class' => 'btn btn-primary']
|
'label' => 'Nom',
|
||||||
]);
|
'attr' => ['class' => 'form-control']
|
||||||
}
|
])
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'S\'inscrire',
|
||||||
|
'attr' => ['class' => 'btn btn-primary']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'data_class' => User::class,
|
'data_class' => User::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
src/Repository/FavorisRepository.php
Normal file
84
src/Repository/FavorisRepository.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Favoris;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<Favoris>
|
||||||
|
*
|
||||||
|
* @method Favoris|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method Favoris|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method Favoris[] findAll()
|
||||||
|
* @method Favoris[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class FavorisRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Favoris::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFavoris(Favoris $favoris): void
|
||||||
|
{
|
||||||
|
$entityManager = $this->getEntityManager();
|
||||||
|
$entityManager->persist($favoris);
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFavoris($userId, $idGoogle): void
|
||||||
|
{
|
||||||
|
// Récupérer l'entity manager
|
||||||
|
$entityManager = $this->getEntityManager();
|
||||||
|
|
||||||
|
// Trouver le favoris de cet utilisateur pour le livre donné
|
||||||
|
$favoris = $this->findOneBy([
|
||||||
|
'user' => $userId,
|
||||||
|
'id_google' => $idGoogle
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$favoris) {
|
||||||
|
throw new \Exception($idGoogle . ' n\'est pas dans les favoris de cet utilisateur' . $userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer l'entité favoris de la base de données
|
||||||
|
$entityManager->remove($favoris);
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFavorisByUser($userId): array
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('f')
|
||||||
|
->andWhere('f.user = :userId')
|
||||||
|
->setParameter('userId', $userId)
|
||||||
|
->getQuery()
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Favoris[] Returns an array of Favoris objects
|
||||||
|
// */
|
||||||
|
// public function findByExampleField($value): array
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('f')
|
||||||
|
// ->andWhere('f.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->orderBy('f.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function findOneBySomeField($value): ?Favoris
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('f')
|
||||||
|
// ->andWhere('f.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
@ -13,20 +13,22 @@ class GoogleBooksService
|
|||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function searchBooks(string $query, string $lang = 'fr'): array
|
public function searchBooks(string $query, string $lang = 'fr', string $nb_pages): array
|
||||||
{
|
{
|
||||||
$url = 'https://www.googleapis.com/books/v1/volumes';
|
$url = 'https://www.googleapis.com/books/v1/volumes';
|
||||||
$response = $this->client->request('GET', $url, [
|
$response = $this->client->request('GET', $url, [
|
||||||
'query' => [
|
'query' => [
|
||||||
'q' => $query,
|
'q' => $query,
|
||||||
'langRestrict' => $lang,
|
'langRestrict' => $lang,
|
||||||
|
'maxResults' => 10,
|
||||||
|
'startIndex' => $nb_pages,
|
||||||
|
'printType' => 'books',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
// Convertir la réponse JSON en tableau PHP
|
// Convertir la réponse JSON en tableau PHP
|
||||||
$dataArray = $response->toArray();
|
$dataArray = $response->toArray();
|
||||||
|
|
||||||
return $dataArray;
|
return $dataArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
symfony.lock
12
symfony.lock
@ -135,6 +135,18 @@
|
|||||||
"templates/base.html.twig"
|
"templates/base.html.twig"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/validator": {
|
||||||
|
"version": "7.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.0",
|
||||||
|
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/validator.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/web-profiler-bundle": {
|
"symfony/web-profiler-bundle": {
|
||||||
"version": "7.0",
|
"version": "7.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -1,23 +1,270 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Hello APITestController!{% endblock %}
|
{% block title %}Recherche | {{ query }}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
<div>
|
||||||
|
<div class="w-1/2 py-2 flex flex-col justify-center mt-5 mx-auto">
|
||||||
<div>
|
<h1 class="text-2xl font-bold">Résultats pour :
|
||||||
{% for book in datas.items %}
|
{{ query }}</h1>
|
||||||
<div class="flex justify-center">
|
</div>
|
||||||
<div class="border rounded-xl w-1/2 py-2 flex flex-col justify-center mt-5">
|
<div class="w-1/2 pb-1 flex flex-col justify-center mx-auto">
|
||||||
<h1 class="font-bold p-2 text-2xl ">{{ book.volumeInfo.title }}</h1>
|
<p>
|
||||||
|
{{ datas.totalItems }} résultats trouvés
|
||||||
{% if book.volumeInfo.imageLinks is defined and book.volumeInfo.imageLinks.smallThumbnail is defined %}
|
</p>
|
||||||
<img src="{{ book.volumeInfo.imageLinks.smallThumbnail }}" class="p-2" style="width: 20%" height="20px">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% for book in datas.items %}
|
||||||
|
{% include '/apiSearch/modal.html.twig' with { 'loop': loop} %}
|
||||||
|
<div class="relative flex rounded-xl border bg-white shadow-lg hover:shadow-xl transition-shadow duration-300 w-1/2 mx-auto py-2 mt-5">
|
||||||
|
<div class="flex flex-col justify-start">
|
||||||
|
<div class="flex flex-col p-2 text-2xl">
|
||||||
|
<h1 class="font-bold">{{ book.volumeInfo.title }}</h1>
|
||||||
|
{% if book.volumeInfo.authors is defined %}
|
||||||
|
<span class="text-lg">{{ book.volumeInfo.authors | join(", ") }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex flex-row w-1/4">
|
||||||
|
{% if book.volumeInfo.imageLinks is defined and book.volumeInfo.imageLinks.smallThumbnail is defined %}
|
||||||
|
<img src="{{ book.volumeInfo.imageLinks.smallThumbnail }}" class="p-2">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="w-9/12">
|
||||||
|
{% if book.volumeInfo.publisher is defined %}
|
||||||
|
<p class="p-2 italic font-bold">Aux éditions :
|
||||||
|
<span class="font-normal">{{ book.volumeInfo.publisher }}</span>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if book.volumeInfo.publishedDate is defined %}
|
||||||
|
<p class="p-2 italic font-bold">Date de publication :
|
||||||
|
<span class="font-normal">{{ book.volumeInfo.publishedDate }}</span>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.categories is defined %}
|
||||||
|
{% for categorie in book.volumeInfo.categories %}
|
||||||
|
<p class="p-2 italic font-bold">Catégorie :
|
||||||
|
<span class="font-normal">{{ categorie }}</span>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<p class="p-2 italic font-bold">Nombres de pages :
|
||||||
|
<span class="font-normal">{{ book.volumeInfo.pageCount }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="p-2 italic font-bold">Description :
|
||||||
|
{% if book.searchInfo is defined %}
|
||||||
|
<span class="font-normal">{{ book.searchInfo.textSnippet | raw }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full p-2">
|
||||||
|
<button id="openModalBtn-{{ loop.index }}" class="dark:bg-[#263B46] text-white px-4 py-2 rounded-lg mt-4 w-full">
|
||||||
|
Voir plus
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% if app.user %}
|
||||||
|
{% set isLiked = book.id in favoris|map(f => f.getIdGoogle()) %}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="{{ isLiked ? 'red' : 'none' }}"
|
||||||
|
stroke="{{ isLiked ? 'red' : 'currentColor' }}"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="like-button absolute top-2 right-2 w-6 h-6 cursor-pointer transition-colors duration-300"
|
||||||
|
data-id-google="{{ book.id }}"
|
||||||
|
data-liked="{{ isLiked ? 'true' : 'false' }}"
|
||||||
|
data-edition="{{ book.volumeInfo.publisher | default('') }}"
|
||||||
|
data-categorie="{{ book.volumeInfo.categories is defined ? book.volumeInfo.categories | join(', ') : '' }}"
|
||||||
|
data-title="{{ book.volumeInfo.title | default('Titre non disponible') }}"
|
||||||
|
data-authors="{{ book.volumeInfo.authors is defined ? book.volumeInfo.authors | join(', ') : '' }}"
|
||||||
|
data-images="{{ book.volumeInfo.imageLinks.smallThumbnail is defined ? book.volumeInfo.imageLinks.smallThumbnail : '' }}"
|
||||||
|
data-description="{{ book.volumeInfo.description is defined and book.volumeInfo.description is not empty ? book.volumeInfo.description | raw : '' }}"
|
||||||
|
data-date="{{ book.volumeInfo.publishedDate | default('') }}"
|
||||||
|
data-pages="{{ book.volumeInfo.pageCount | default('0') }}">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<div id="myModal-{{ loop.index }}" class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center hidden z-50">
|
||||||
|
<div class="relative bg-white p-6 rounded-lg w-1/3">
|
||||||
|
<div class="flex flex-row w-1/4">
|
||||||
|
{% if book.volumeInfo.imageLinks is defined and book.volumeInfo.imageLinks.smallThumbnail is defined %}
|
||||||
|
<img src="{{ book.volumeInfo.imageLinks.smallThumbnail }}" class="p-2">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<svg id="closeModalSvg-{{ loop.index }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="absolute top-2 right-2 w-6 h-6 cursor-pointer text-gray-500 hover:text-gray-700">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{% if book.volumeInfo.title is defined and book.volumeInfo.title is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Titre : <span class="font-normal">{{ book.volumeInfo.title }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.authors is defined %}
|
||||||
|
<p class="p-2 italic font-bold"> Auteur(s) : <span class="font-normal"> {{ book.volumeInfo.authors | join(", ") }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.publisher is defined and book.volumeInfo.publisher is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Aux éditions : <span class="font-normal">{{ book.volumeInfo.publisher }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.publishedDate is defined and book.volumeInfo.publishedDate is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Date de publication : <span class="font-normal">{{ book.volumeInfo.publishedDate }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.categories is defined and book.volumeInfo.categories is not empty %}
|
||||||
|
{% for categorie in book.volumeInfo.categories %}
|
||||||
|
<p class="p-2 italic font-bold">Catégorie : <span class="font-normal">{{ categorie }}</span></p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.pageCount is defined and book.volumeInfo.pageCount is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Nombre de pages : <span class="font-normal">{{ book.volumeInfo.pageCount }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo is defined and book.volumeInfo.description is defined and book.volumeInfo.description is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Description : <span class="font-normal">{{ book.volumeInfo.description | raw }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<button id="closeModalBtn-{{ loop.index }}" class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 mt-4">
|
||||||
|
Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% set nb_items = datas.totalItems %}
|
||||||
|
{% set nb_pages = nb_items / 10 %}
|
||||||
|
{% set current_page = app.request.get('page') ? app.request.get('page') : 1 %}
|
||||||
|
|
||||||
|
{% if nb_pages > 1 %}
|
||||||
|
<div class="flex justify-center mt-8 mb-8">
|
||||||
|
<ul class="flex">
|
||||||
|
{# Flèche "Précédent" #}
|
||||||
|
{% if current_page > 1 %}
|
||||||
|
<li class="mx-2">
|
||||||
|
<a href="{{ path('api_search', {'q': query, 'page': current_page - 1}) }}" class="px-3 py-3 dark:bg-[#263B46] text-white rounded-lg mr-32">Précédent</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="mx-2">
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Liens des pages proches de la page actuelle #}
|
||||||
|
{% for i in current_page - 3..current_page + 3 %}
|
||||||
|
{% if i > 0 and i <= nb_pages %}
|
||||||
|
<li class="mx-2">
|
||||||
|
<a href="{{ path('api_search', {'q': query, 'page': i}) }}" class="px-4 py-3 text-white {{ i == current_page ? 'dark:bg-[#9db1bd] text-gray-700' : 'bg-gray-200 text-black' }} rounded-lg">{{ i }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{# Flèche "Suivant" #}
|
||||||
|
{% if current_page < nb_pages %}
|
||||||
|
<li class="mx-2">
|
||||||
|
<a href="{{ path('api_search', {'q': query, 'page': current_page + 1}) }}" class="px-3 py-3 dark:bg-[#263B46] text-white rounded-lg ml-32">Suivant</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="mx-2">
|
||||||
|
<span class="px-3 py-3 dark:bg-[#263B46] text-white rounded-lg cursor-not-allowed ml-32">Suivant</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleLike(idGoogle, liked, element, bookDetails) {
|
||||||
|
const url = liked
|
||||||
|
? "{{ path('unlike', {'idGoogle': 'PLACEHOLDER'}) }}".replace('PLACEHOLDER', idGoogle)
|
||||||
|
: "{{ path('like', {'idGoogle': 'PLACEHOLDER'}) }}".replace('PLACEHOLDER', idGoogle);
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(bookDetails)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if ((liked && data.status === 'removed') || (!liked && data.status === 'added')) {
|
||||||
|
// Mettre à jour l'état visuel
|
||||||
|
const newLikedState = !liked;
|
||||||
|
element.setAttribute('data-liked', newLikedState.toString());
|
||||||
|
element.querySelector('path').setAttribute('fill', newLikedState ? 'red' : 'none');
|
||||||
|
element.querySelector('path').setAttribute('stroke', newLikedState ? 'red' : 'currentColor');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Erreur AJAX', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.like-button').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const idGoogle = this.dataset.idGoogle;
|
||||||
|
const liked = this.getAttribute('data-liked') === 'true'; // Lire l'état actuel
|
||||||
|
const bookDetails = {
|
||||||
|
title: this.dataset.title,
|
||||||
|
authors: this.dataset.authors,
|
||||||
|
images: this.dataset.images,
|
||||||
|
description: this.dataset.description,
|
||||||
|
date: this.dataset.date,
|
||||||
|
pages: this.dataset.pages,
|
||||||
|
edition: this.dataset.edition,
|
||||||
|
categorie: this.dataset.categorie
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleLike(idGoogle, liked, this, bookDetails);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
{% for book in datas.items %}
|
||||||
|
const openModalBtn{{ loop.index }} = document.getElementById('openModalBtn-{{ loop.index }}');
|
||||||
|
const closeModalSvg{{ loop.index }} = document.getElementById('closeModalSvg-{{ loop.index }}');
|
||||||
|
const closeModalBtn{{ loop.index }} = document.getElementById('closeModalBtn-{{ loop.index }}');
|
||||||
|
const modal{{ loop.index }} = document.getElementById('myModal-{{ loop.index }}');
|
||||||
|
|
||||||
|
if (openModalBtn{{ loop.index }}) {
|
||||||
|
openModalBtn{{ loop.index }}.addEventListener('click', () => {
|
||||||
|
modal{{ loop.index }}.classList.remove('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeModalSvg{{ loop.index }}) {
|
||||||
|
closeModalSvg{{ loop.index }}.addEventListener('click', () => {
|
||||||
|
modal{{ loop.index }}.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeModalBtn{{ loop.index }}) {
|
||||||
|
closeModalBtn{{ loop.index }}.addEventListener('click', () => {
|
||||||
|
modal{{ loop.index }}.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
76
templates/apiSearch/modal.html.twig
Normal file
76
templates/apiSearch/modal.html.twig
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div id="myModal-{{ loop.index }}" class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center hidden z-50">
|
||||||
|
<div class="relative bg-white p-6 rounded-lg w-1/3 max-h-screen overflow-y-auto">
|
||||||
|
<div class="flex mx-auto flex-row w-1/4">
|
||||||
|
{% if book.volumeInfo.imageLinks is defined and book.volumeInfo.imageLinks.smallThumbnail is defined %}
|
||||||
|
<img src="{{ book.volumeInfo.imageLinks.smallThumbnail }}" class="p-2">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<svg id="closeModalSvg-{{ loop.index }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="absolute top-2 right-2 w-6 h-6 cursor-pointer text-gray-500 hover:text-gray-700">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{% if book.volumeInfo.title is defined and book.volumeInfo.title is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Titre : <span class="font-normal">{{ book.volumeInfo.title }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.authors is defined %}
|
||||||
|
<p class="p-2 italic font-bold"> Auteur(s) : <span class="font-normal"> {{ book.volumeInfo.authors | join(", ") }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.publisher is defined and book.volumeInfo.publisher is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Aux éditions : <span class="font-normal">{{ book.volumeInfo.publisher }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.publishedDate is defined and book.volumeInfo.publishedDate is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Date de publication : <span class="font-normal">{{ book.volumeInfo.publishedDate }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.categories is defined and book.volumeInfo.categories is not empty %}
|
||||||
|
{% for categorie in book.volumeInfo.categories %}
|
||||||
|
<p class="p-2 italic font-bold">Catégorie : <span class="font-normal">{{ categorie }}</span></p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo.pageCount is defined and book.volumeInfo.pageCount is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Nombre de pages : <span class="font-normal">{{ book.volumeInfo.pageCount }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.volumeInfo is defined and book.volumeInfo.description is defined and book.volumeInfo.description is not empty %}
|
||||||
|
<p class="p-2 italic font-bold">Description : <span class="font-normal">{{ book.volumeInfo.description | raw }}</span></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr class="mt-6">
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="font-bold text-lg">Laisser un commentaire :</h3>
|
||||||
|
<form id="commentForm-{{ loop.index }}" action="" method="POST" class="space-y-4">
|
||||||
|
<textarea id="commentText-{{ loop.index }}" name="comment" rows="4" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="Écrivez votre commentaire ici..." required></textarea>
|
||||||
|
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">Envoyer</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="mt-8">
|
||||||
|
<div id="commentsSection-{{ loop.index }}" class="mt-4 max-h-60 overflow-y-auto">
|
||||||
|
<h3 class="font-bold text-lg">Commentaires :</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="p-2 border-b border-gray-200">
|
||||||
|
<p class="font-semibold">Auteur : Fabien</p>
|
||||||
|
<p>Très bon livre pour les débutants.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="closeModalBtn-{{ loop.index }}" class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 mt-4">
|
||||||
|
Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{{ asset('js/modal.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -23,5 +23,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -21,19 +21,19 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Left navigation links -->
|
<!-- Left navigation links -->
|
||||||
<ul class="list-style-none me-auto flex flex-col ps-0 lg:flex-row" data-twe-navbar-nav-ref>
|
<ul class="list-style-none me-auto flex flex-col ps-0 lg:flex-row" data-twe-navbar-nav-ref>
|
||||||
<li
|
<li>
|
||||||
class="mb-4 lg:mb-0 lg:pe-2" data-twe-nav-item-ref>
|
</li>
|
||||||
<!-- Dashboard link -->
|
{% if app.user is empty %}
|
||||||
<a class="text-neutral-500 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 lg:px-2 [&.active]:text-black/90 dark:[&.active]:text-zinc-400" href="/" data-twe-nav-link-ref>Accueil</a>
|
<!-- Inscription link -->
|
||||||
</li>
|
|
||||||
<!-- Team link -->
|
|
||||||
<li class="mb-4 lg:mb-0 lg:pe-2" data-twe-nav-item-ref>
|
<li class="mb-4 lg:mb-0 lg:pe-2" data-twe-nav-item-ref>
|
||||||
<a class="text-neutral-500 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 lg:px-2 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="/registration" data-twe-nav-link-ref>Inscription</a>
|
<a class="text-neutral-500 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 lg:px-2 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="/registration" data-twe-nav-link-ref>Inscription</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- Projects link -->
|
<!-- login link -->
|
||||||
<li class="mb-4 lg:mb-0 lg:pe-2" data-twe-nav-item-ref>
|
<li class="mb-4 lg:mb-0 lg:pe-2" data-twe-nav-item-ref>
|
||||||
<a class="text-neutral-500 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 lg:px-2 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="/login" data-twe-nav-link-ref>Connexion</a>
|
<a class="text-neutral-500 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 lg:px-2 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="/login" data-twe-nav-link-ref>Connexion</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -57,28 +57,32 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="me-4 text-neutral-600 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="#">
|
|
||||||
<span class="[&>svg]:w-5">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="currentColor" class="h-5 w-5">
|
|
||||||
<path d="M2.25 2.25a.75.75 0 000 1.5h1.386c.17 0 .318.114.362.278l2.558 9.592a3.752 3.752 0 00-2.806 3.63c0 .414.336.75.75.75h15.75a.75.75 0 000-1.5H5.378A2.25 2.25 0 017.5 15h11.218a.75.75 0 00.674-.421 60.358 60.358 0 002.96-7.228.75.75 0 00-.525-.965A60.864 60.864 0 005.68 4.509l-.232-.867A1.875 1.875 0 003.636 2.25H2.25zM3.75 20.25a1.5 1.5 0 113 0 1.5 1.5 0 01-3 0zM16.5 20.25a1.5 1.5 0 113 0 1.5 1.5 0 01-3 0z"/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
{% if app.user %}
|
||||||
class="relative flex items-center">
|
<div
|
||||||
<!-- Cart Icon -->
|
class="relative flex items-center">
|
||||||
<a class="me-4 text-neutral-600 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="#">
|
<!-- Cart Icon -->
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewbox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
<a class="me-4 text-neutral-600 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="/favoris">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"/>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewbox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<a class="me-4 text-neutral-600 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 [&.active]:text-black/90 dark:[&.active]:text-neutral-400" href="/logout">
|
||||||
|
<span class="[&>svg]:w-5">
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15m-3 0-3-3m0 0 3-3m-3 3H15" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
</span>
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
11
templates/favoris/index.html.twig
Normal file
11
templates/favoris/index.html.twig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Mes favoris{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% for data in datas %}
|
||||||
|
{% for book in data %}
|
||||||
|
<p>{{ book.title }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
@ -24,25 +24,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if error %}
|
|
||||||
<div class="text-red-500 text-sm mt-2">
|
|
||||||
{{ error.messageKey|trans(error.messageData, 'security') }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="mt-6 text-center">
|
<div class="mt-6 text-center">
|
||||||
<button type="submit" class="w-full px-4 py-2 bg-indigo-600 text-white font-semibold rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
<button type="submit" class="w-full px-4 py-2 bg-indigo-600 text-white font-semibold rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||||
Se connecter
|
Se connecter
|
||||||
</button>
|
</button>
|
||||||
{% for type, messages in app.flashes %}
|
|
||||||
<div class="mb-4">
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="text-sm {% if type == 'success' %}text-green-600{% else %}text-red-600{% endif %}">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
{# templates/registration/register.html.twig #}
|
|
||||||
|
|
||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Inscription{% endblock %}
|
{% block title %}Inscription{% endblock %}
|
||||||
@ -11,30 +9,6 @@
|
|||||||
|
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
{{ form_label(form.email, 'Email', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
|
||||||
<div class="mt-1">
|
|
||||||
{{ form_widget(form.email, {'attr': {'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}}) }}
|
|
||||||
</div>
|
|
||||||
{{ form_errors(form.email) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
{{ form_label(form.password, 'Mot de passe', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
|
||||||
<div class="mt-1">
|
|
||||||
{{ form_widget(form.password, {'attr': {'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}}) }}
|
|
||||||
</div>
|
|
||||||
{{ form_errors(form.password) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
{{ form_label(form.pseudo, 'Pseudo', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
|
||||||
<div class="mt-1">
|
|
||||||
{{ form_widget(form.pseudo, {'attr': {'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}}) }}
|
|
||||||
</div>
|
|
||||||
{{ form_errors(form.pseudo) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ form_label(form.firstname, 'Prénom', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
{{ form_label(form.firstname, 'Prénom', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
@ -51,27 +25,81 @@
|
|||||||
{{ form_errors(form.lastname) }}
|
{{ form_errors(form.lastname) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form_label(form.pseudo, 'Pseudo', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
||||||
|
<div class="mt-1">
|
||||||
|
{{ form_widget(form.pseudo, {
|
||||||
|
'attr': {
|
||||||
|
'class': form.pseudo.vars.errors|length > 0 ? 'w-full px-3 py-2 border border-red-500 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm' : 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'
|
||||||
|
}
|
||||||
|
}) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-red-600 mt-2">
|
||||||
|
{{ form_errors(form.pseudo) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form_label(form.email, 'Email', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
||||||
|
<div class="mt-1">
|
||||||
|
{{ form_widget(form.email, {
|
||||||
|
'attr': {
|
||||||
|
'class': form.email.vars.errors|length > 0 ? 'w-full px-3 py-2 border border-red-500 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm' : 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'
|
||||||
|
}
|
||||||
|
}) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-red-600 mt-2">
|
||||||
|
{{ form_errors(form.email) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form_label(form.password, 'Mot de passe', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
||||||
|
<div class="mt-1">
|
||||||
|
{{ form_widget(form.password, {'attr': {'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}}) }}
|
||||||
|
</div>
|
||||||
|
{{ form_errors(form.password) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form_label(form.confirmPassword, 'Confirmer le mot de passe', {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}}) }}
|
||||||
|
<div class="mt-1">
|
||||||
|
{{ form_widget(form.confirmPassword, {'attr': {'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}}) }}
|
||||||
|
</div>
|
||||||
|
{{ form_errors(form.confirmPassword) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 text-center">
|
<div class="mt-6 text-center">
|
||||||
{{ form_widget(form.submit, {
|
{{ form_widget(form.submit, {
|
||||||
'attr': {'class': 'w-full px-4 py-2 bg-indigo-600 text-white font-semibold rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500'}
|
'attr': {'class': 'w-full px-4 py-2 bg-indigo-600 text-white font-semibold rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500'}
|
||||||
}) }}
|
}) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for type, messages in app.flashes %}
|
{% for type, messages in app.flashes %}
|
||||||
<div class="mb-4">
|
<div class="mb-4 mt-4">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="text-sm {% if type == 'success' %}text-green-600{% else %}text-red-600{% endif %}">
|
<div class="text-sm p-4 rounded-lg shadow-lg
|
||||||
{{ message }}
|
{% if type == 'success' %}
|
||||||
|
bg-green-100 text-green-700 border-l-4 border-green-500
|
||||||
|
{% else %}
|
||||||
|
bg-red-100 text-red-700 border-l-4 border-red-500
|
||||||
|
{% endif %}
|
||||||
|
transition-all transform hover:scale-105">
|
||||||
|
<div class="font-semibold">
|
||||||
|
{% if type == 'success' %}
|
||||||
|
Succès:
|
||||||
|
{% else %}
|
||||||
|
Erreur:
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p>{{ message }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
{% if error %}
|
|
||||||
<div class="text-red-500 text-sm mt-2">
|
|
||||||
{{ error.messageKey|trans(error.messageData, 'security') }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Reference in New Issue
Block a user