Skip to content
Snippets Groups Projects
Commit f2d84c49 authored by user's avatar user
Browse files
parents 49a7acb3 840f7fad
No related branches found
No related tags found
No related merge requests found
Showing with 547 additions and 134 deletions
......@@ -13,11 +13,17 @@ class HomeController extends AbstractController
#[Route('/', name: 'homepage')]
public function index(): Response
{
$joint_creation_URL = isset($_GET["joint_creation_URL"]) ? $_GET["joint_creation_URL"] : null;
$user = $this->getUser(); // Récupère l'utilisateur connecté
$links = [
];
if ($joint_creation_URL) {
$links["joint_creation_URL"] = $joint_creation_URL;
}
// Ajoutez le lien "Admin Dashboard" uniquement si l'utilisateur est admin
if ($user && $user->isAdmin()) {
$links['Admin Dashboard'] = $this->generateUrl('admin_dashboard');
......@@ -37,6 +43,8 @@ class HomeController extends AbstractController
}
return $this->render('home/index.html.twig', [
......
<?php
namespace App\Controller;
use App\Entity\Invitation;
use App\Entity\Wishlist;
use App\Form\InvitationType;
use App\Repository\InvitationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/invitation')]
final class InvitationController extends AbstractController
{
#[Route(name: 'app_invitation_index', methods: ['GET'])]
public function index(InvitationRepository $invitationRepository): Response
{
return $this->render('invitation/index.html.twig', [
'invitations' => $invitationRepository->findAll(),
]);
}
#[Route('/new', name: 'app_invitation_new', methods: ['GET', 'POST'])]
public function createInvitation(Request $request, EntityManagerInterface $entityManager): Response
{
$invitation = new Invitation();
$user = $this->getUser() ;
if (!$user) {
return $this->createAccessDeniedException('User is not connected');
}
$invitation->setInviter($user);
$invitation->setWishlist($entityManager->find(Wishlist::class, $request->get(key: 'wishlist_id')));
$entityManager->persist($invitation);
$entityManager->flush();
return new JsonResponse(["joint_creation_URL"=> InvitationController::generateJointCreationURL($invitation->getId())] , Response::HTTP_CREATED);
}
#[Route('/{id}', name: 'app_invitation_show', methods: ['GET'])]
public function show(Invitation $invitation): Response
{
return $this->render('invitation/show.html.twig', [
'invitation' => $invitation,
]);
}
#[Route('/{id}/edit', name: 'app_invitation_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Invitation $invitation, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(InvitationType::class, $invitation);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('app_invitation_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('invitation/edit.html.twig', [
'invitation' => $invitation,
'form' => $form,
]);
}
#[Route('/{id}', name: 'app_invitation_delete', methods: ['POST'])]
public function delete(Request $request, Invitation $invitation, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$invitation->getId(), $request->getPayload()->getString('_token'))) {
$entityManager->remove($invitation);
$entityManager->flush();
}
return $this->redirectToRoute('app_invitation_index', [], Response::HTTP_SEE_OTHER);
}
private function generateJointCreationURL(int $invitation_id): string {
$secretKey = 'top_secret_key_789/*-';
$hash = hash_hmac('sha256', (string) $invitation_id, $secretKey);
$token = base64_encode($invitation_id . '|' . $hash);
$serverIp = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1';
return sprintf('http://%s?invitation_token=%s', $serverIp, rtrim(strtr($token, '+/', '-_'), '='));
}
private function verifyJointCreationToken(string $token): ?int {
$secretKey = 'top_secret_key_789/*-';
$token = strtr($token, '-_', '+/');
$token = base64_decode($token);
if (!$token) {
return null;
}
$parts = explode('|', $token);
if (count($parts) !== 2) {
return null;
}
[$invitation_id, $hash] = $parts;
$expectedHash = hash_hmac('sha256', $invitation_id, $secretKey);
if (!hash_equals($expectedHash, $hash)) {
return null;
}
return (int) $invitation_id;
}
}
/* $form = $this->createForm(InvitationType::class, $invitation);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($invitation);
$entityManager->flush();
return $this->redirectToRoute('app_invitation_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('invitation/new.html.twig', [
'invitation' => $invitation,
'form' => $form,
]); */
\ No newline at end of file
......@@ -20,22 +20,39 @@ class RegistrationController extends AbstractController
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Hacher le mot de passe
$hashedPassword = $passwordHasher->hashPassword($user, plainPassword: $form->get('password')->getData());
$user->setPassword($hashedPassword);
if ($form->isSubmitted() ) {
if ($form->isValid()){
// Hacher le mot de passe
$hashedPassword = $passwordHasher->hashPassword($user, plainPassword: $form->get('password')->getData());
$user->setPassword($hashedPassword);
// Sauvegarder l'utilisateur
$entityManager->persist($user);
$entityManager->flush();
// Sauvegarder l'utilisateur
$entityManager->persist($user);
$entityManager->flush();
// Rediriger vers la page de connexion
return $this->redirectToRoute('login');
}
if (!$form->isValid()) {
$errors = [];
foreach ($form->getErrors(true) as $error) {
$errors[] = $error->getMessage();
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
'formErrors' => $errors,
]);
}
// Rediriger vers la page de connexion
return $this->redirectToRoute('login');
}
else {
// Si le formulaire n'est pas valide, les erreurs seront disponibles dans la vue
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
......
<?php
namespace App\Entity;
use App\Repository\InvitationRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: InvitationRepository::class)]
class Invitation
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
private ?Wishlist $wishlist = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
private ?User $inviter = null;
public function getId(): ?int
{
return $this->id;
}
public function getWishlist(): ?Wishlist
{
return $this->wishlist;
}
public function setWishlist(Wishlist $wishlist): static
{
$this->wishlist = $wishlist;
return $this;
}
public function getInviter(): ?User
{
return $this->inviter;
}
public function setInviter(User $inviter): static
{
$this->inviter = $inviter;
return $this;
}
}
......@@ -65,13 +65,22 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\OneToMany(mappedBy: 'buyer', targetEntity: PurchaseProof::class, cascade: ['persist', 'remove'])]
private Collection $purchaseProofs;
/**
* @var Collection<int, Invitation>
*/
#[ORM\ManyToMany(targetEntity: Invitation::class)]
private Collection $invitations;
public function __construct()
{
$this->wishlists = new ArrayCollection();
$this->invitations = new ArrayCollection();
$this->roles = ['ROLE_USER'];
$this->isLocked = false;
$this->invitations = new ArrayCollection();
}
public function getId(): ?int
......@@ -187,4 +196,29 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
// }
/**
* @return Collection<int, Invitation>
*/
public function getInvitations(): Collection
{
return $this->invitations;
}
public function addInvitation(Invitation $invitation): static
{
if (!$this->invitations->contains($invitation)) {
$this->invitations->add($invitation);
}
return $this;
}
public function removeInvitation(Invitation $invitation): static
{
$this->invitations->removeElement($invitation);
return $this;
}
}
<?php
namespace App\Form;
use App\Entity\Invitation;
use App\Entity\Wishlist;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class InvitationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('wishlist', EntityType::class, [
'class' => Wishlist::class,
'choice_label' => 'id',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Invitation::class,
]);
}
}
<?php
namespace App\Repository;
use App\Entity\Invitation;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Invitation>
*/
class InvitationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Invitation::class);
}
// /**
// * @return Invitation[] Returns an array of Invitation objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('i')
// ->andWhere('i.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('i.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Invitation
// {
// return $this->createQueryBuilder('i')
// ->andWhere('i.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
<form method="post" action="{{ path('app_invitation_delete', {'id': invitation.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ invitation.id) }}">
<button class="btn">Delete</button>
</form>
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
{% extends 'base.html.twig' %}
{% block title %}Edit Invitation{% endblock %}
{% block body %}
<h1>Edit Invitation</h1>
{{ include('invitation/_form.html.twig', {'button_label': 'Update'}) }}
<a href="{{ path('app_invitation_index') }}">back to list</a>
{{ include('invitation/_delete_form.html.twig') }}
{% endblock %}
{% extends 'base.html.twig' %}
{% block title %}Invitation index{% endblock %}
{% block body %}
<h1>Invitation index</h1>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>actions</th>
</tr>
</thead>
<tbody>
{% for invitation in invitations %}
<tr>
<td>{{ invitation.id }}</td>
<td>
<a href="{{ path('app_invitation_show', {'id': invitation.id}) }}">show</a>
<a href="{{ path('app_invitation_edit', {'id': invitation.id}) }}">edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="2">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ path('app_invitation_new') }}">Create new</a>
{% endblock %}
{% extends 'base.html.twig' %}
{% block title %}New Invitation{% endblock %}
{% block body %}
<h1>Create new Invitation</h1>
{{ include('invitation/_form.html.twig') }}
<a href="{{ path('app_invitation_index') }}">back to list</a>
{% endblock %}
{% extends 'base.html.twig' %}
{% block title %}Invitation{% endblock %}
{% block body %}
<h1>Invitation</h1>
<table class="table">
<tbody>
<tr>
<th>Id</th>
<td>{{ invitation.id }}</td>
</tr>
</tbody>
</table>
<a href="{{ path('app_invitation_index') }}">back to list</a>
<a href="{{ path('app_invitation_edit', {'id': invitation.id}) }}">edit</a>
{{ include('invitation/_delete_form.html.twig') }}
{% endblock %}
{% extends 'base.html.twig' %}
{% block title %}My Wishlists{% endblock %}
{% block body %}
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #00B8DE, #99CC33) fixed;
color: white;
text-align: center;
padding: 20px;
}
.container {
max-width: 800px;
margin: auto;
display: flex;
flex-direction: column;
gap: 15px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.search-bar {
padding: 8px;
border-radius: 8px;
border: 2px solid #99CC33;
}
.add-wishlist-btn {
background: white;
color: #00B8DE;
padding: 10px 15px;
text-decoration: none;
border-radius: 8px;
font-weight: bold;
transition: 0.3s;
display: inline-block;
margin: 15px 0;
}
.add-wishlist-btn:hover {
background: #99CC33;
color: white;
transform: scale(1.1);
}
.wishlist {
background: rgba(255, 255, 255, 0.2);
padding: 15px;
border-radius: 10px;
border: 2px solid #99CC33;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 600px;
margin: auto;
}
.wishlist h2 {
margin: 10px 0;
}
.wishlist-items {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.wishlist-item {
background: white;
color: #00B8DE;
padding: 10px;
border-radius: 5px;
font-size: 1.5em;
}
.wishlist-footer {
margin-top: 10px;
font-size: 0.9em;
color: #e0f7fa;
}
.wishlist-actions {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 10px;
}
.wishlist-actions button {
background: white;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
transition: 0.3s;
}
.wishlist-actions button:hover {
background: #99CC33;
color: white;
}
@media (max-width: 768px) {
.wishlist {
width: 90%;
}
}
{% block title %} My Wishlists {% endblock %}
{% block body %}
<style>
.modal {
display: none; /* Hide by default */
position: fixed;
z-index: 1000;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
.modal-content {
text-align: center;
}
.close {
position: absolute;
top: 10px;
right: 20px;
font-size: 24px;
cursor: pointer;
}
</style>
<header>
......@@ -111,31 +43,94 @@
<div class="container">
{% for wishlist in wishlists %}
<div class="wishlist">
<h2>
<a href="{{ path('app_wishlist_show', { 'id': wishlist.id }) }}">
{{ wishlist.name }}
</a>
</h2>
<div class="wishlist-items">
{% for item in wishlist.items %}
<div class="wishlist-item">📷</div>
{% else %}
<p class="empty-state">No items yet.</p>
{% endfor %}
</div>
<p class="wishlist-footer">{{ wishlist.deadline ? wishlist.deadline|date('Y-m-d') : 'No deadline' }}</p>
<div class="wishlist-actions">
<button title="Share wishlist"></button>
<button title="Edit title"></button>
<button title="Delete wishlist">🗑</button>
</div>
<div class="wishlist">
<h2>{{ wishlist.name }}</h2>
<div class="wishlist-items">
{% for item in wishlist.items %}
<div class="wishlist-item">📷</div>
{% endfor %}
</div>
{% else %}
<p>You have no wishlists yet. Start by <a href="{{ path('app_wishlist_new') }}">creating one</a>.</p>
<p class="wishlist-footer">{{ wishlist.deadline | date('F j, Y') }}</p>
<!-- Share Button -->
<button type="button" class="share-btn" data-wishlist-id="{{ wishlist.id }}"></button>
<button title="Edit title"></button>
<button title="Delete wishlist">🗑</button>
</div>
{% endfor %}
</div>
{% endblock %}
\ No newline at end of file
<!-- Modal Popup -->
<div id="share-modal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h3>Share Wishlist</h3>
<button id="invite-co-worker">Invite a Co-Worker</button>
<button id="share-wishes">Share Your Wishes!</button>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const shareButtons = document.querySelectorAll(".share-btn");
const modal = document.getElementById("share-modal");
const closeBtn = document.querySelector(".close");
let selectedWishlistId = null; // Store the selected wishlist ID
shareButtons.forEach(button => {
button.addEventListener("click", function () {
selectedWishlistId = this.dataset.wishlistId;
modal.style.display = "block";
});
});
closeBtn.addEventListener("click", function () {
modal.style.display = "none";
});
window.addEventListener("click", function (event) {
if (event.target === modal) {
modal.style.display = "none";
}
});
// Handle Invite a Co-Worker Click
document.getElementById("invite-co-worker").addEventListener("click", async function () {
try {
const response = await fetch("{{path('app_invitation_new')}}", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ wishlist_id: selectedWishlistId })
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const responseData = await response.json();
navigator.clipboard.writeText(responseData.url).then(() => {
alert("URL was copied to clipboard successfully!");
}).catch(err => {
alert("Error copying URL to clipboard: " + err);
});
} catch (error) {
console.error("Error:", error);
alert("Failed to send invitation.");
}
});
// Handle Share Your Wishes Click
document.getElementById("share-wishes").addEventListener("click", function () {
alert("Sharing your wishes! (Implement your sharing logic here)");
});
});
</script>
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment