Compare commits

...

8 Commits

12 changed files with 279 additions and 113 deletions

View File

@ -1,28 +0,0 @@
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');
});
}
});
});

BIN
public/img/logo-cropped.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -9,6 +9,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\FavorisRepository;
use App\Entity\Favoris;
use Symfony\Component\HttpFoundation\JsonResponse;
class APISearchController extends AbstractController
{
@ -31,27 +32,51 @@ class APISearchController extends AbstractController
}
#[Route('/toggleLike/{idGoogle}', name: 'like', methods: "POST")]
public function addFavoris(FavorisRepository $favorisRepository, String $idGoogle)
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é']);
return $this->json(['success' => true, 'message' => 'Favoris ajouté', "status" => "added"]);
}
#[Route('/untoggleLike/{idGoogle}', name: 'unlike', methods: "POST")]
public function removeFavoris(FavorisRepository $favorisRepository, String $idGoogle)
{
$favoris = new Favoris();
$user = $this->getUser();
$favoris->setUser($user);
$favoris->setIdGoogle($idGoogle);
$favorisRepository->removeFavoris($user, $idGoogle);
return $this->json(['success' => true, 'message' => 'Favoris supprimé']);
return $this->json(['success' => true, 'message' => 'Favoris supprimé', "status" => "removed"]);
}
#[Route('/api/search', name: 'api_search')]

View File

@ -22,10 +22,8 @@ class FavorisController extends AbstractController
public function index(FavorisRepository $favorisRepository): Response
{
$favorisUser = $favorisRepository->findBy(['user' => $this->getUser()]);
foreach ($favorisUser as $favori) {
array_push($this->favorisResult, $this->googleBooksService->searchBooks($favori->getIdGoogle(), 'fr', 0));
}
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,

View File

@ -66,6 +66,7 @@ class RegistrationController extends AbstractController
$this->entityManager->flush();
$this->addFlash('success', 'Votre compte a été créé avec succès !');
return $this->redirectToRoute('home');
}
}

View File

@ -19,6 +19,30 @@ class Favoris
#[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;
@ -47,4 +71,100 @@ class Favoris
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;
}
}

View File

@ -39,9 +39,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Favoris::class)]
private Collection $favoris;
#[ORM\OneToMany(mappedBy: 'id_user', targetEntity: Avis::class)]
private Collection $avis;
public function __construct()
{
$this->favoris = new ArrayCollection();
$this->avis = new ArrayCollection();
}
@ -169,4 +173,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
/**
* @return Collection<int, Avis>
*/
public function getAvis(): Collection
{
return $this->avis;
}
public function addAvi(Avis $avi): static
{
if (!$this->avis->contains($avi)) {
$this->avis->add($avi);
$avi->setIdUser($this);
}
return $this;
}
public function removeAvi(Avis $avi): static
{
if ($this->avis->removeElement($avi)) {
// set the owning side to null (unless already changed)
if ($avi->getIdUser() === $this) {
$avi->setIdUser(null);
}
}
return $this;
}
}

View File

@ -170,6 +170,7 @@
},
"files": [
"assets/app.js",
"assets/js/modal.js",
"assets/styles/app.css",
"config/packages/webpack_encore.yaml",
"package.json",

View File

@ -24,12 +24,15 @@
{% 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">
<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 w-[200px] h-[250] object-cover">
{% else %}
<img src="https://fakeimg.pl/550x750?text=no+cover" class="p-2 object-cover">
{% 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>
@ -69,19 +72,29 @@
{% if app.user %}
{% set isLiked = book.id in favoris|map(f => f.getIdGoogle()) %}
<svg
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 transition-colors duration-300"
x-data="{ liked: {{ isLiked ? 'true' : 'false' }} }"
:fill="liked ? 'red' : 'none'"
:stroke="liked ? 'red' : 'currentColor'"
@click="toggleLike('{{ book.id }}', liked, $el); liked = !liked">
<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>
<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 %}
@ -181,47 +194,53 @@
</div>
<script>
function toggleLike(idGoogle, liked, element) {
if (liked) {
fetch("{{ path('unlike', {'idGoogle': 'PLACEHOLDER'}) }}".replace('PLACEHOLDER', idGoogle), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({})
}).then(response => response.json()).then(data => {
if (data.status === 'removed') {
console.log('Livre retiré des favoris');
// Mettre à jour l'état visuel
element.setAttribute('x-data', '{ liked: false }');
element.querySelector('path').setAttribute('fill', 'none');
element.querySelector('path').setAttribute('stroke', 'currentColor');
}
}).catch(error => {
console.error('Erreur AJAX', error);
});
} else {
fetch("{{ path('like', {'idGoogle': 'PLACEHOLDER'}) }}".replace('PLACEHOLDER', idGoogle), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({})
}).then(response => response.json()).then(data => {
if (data.status === 'added') {
console.log('Livre ajouté aux favoris');
// Mettre à jour l'état visuel
element.setAttribute('x-data', '{ liked: true }');
element.querySelector('path').setAttribute('fill', 'red');
element.querySelector('path').setAttribute('stroke', 'red');
}
}).catch(error => {
console.error('Erreur AJAX', error);
});
}
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 }}');
@ -249,6 +268,8 @@
{% endfor %}
});
</script>
{% endblock %}

View File

@ -2,9 +2,11 @@
<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">
<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">
<img src="{{ book.volumeInfo.imageLinks.smallThumbnail }}" class="p-2 w-[200px] h-[250] object-cover">
{% else %}
<img src="https://fakeimg.pl/550x750?text=no+cover" class="p-2 object-cover">
{% endif %}
</div>
<svg id="closeModalSvg-{{ loop.index }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"

View File

@ -7,7 +7,7 @@
{% block title %}Bienvenue!
{% 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></svg>">
<link rel="icon" href="{{ asset('img/logo-cropped.ico') }}" type="image/x-icon"/>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}

View File

@ -3,17 +3,9 @@
{% block title %}Mes favoris{% endblock %}
{% block body %}
<div>
{% for data in datas %}
{% if data.items is defined %}
{% for book in data.items %}
{% if book.volumeInfo.authors is defined %}
<p>{{ book.volumeInfo.title }} - {{ book.volumeInfo.authors | join(", ") }} </p>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% for data in datas %}
{% for book in data %}
<p>{{ book.title }}</p>
{% endfor %}
{% endfor %}
{% endblock %}