Implement flash messages, refactor candidate retrieval, and enhance quiz functionality

This commit is contained in:
2025-03-04 22:20:35 +01:00
parent 26b99a8353
commit 29bc74fe4f
9 changed files with 88 additions and 17 deletions

View File

@@ -4,11 +4,14 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\Entity\Candidate;
use App\Entity\Season; use App\Entity\Season;
use App\Enum\FlashType;
use App\Form\EnterNameType; use App\Form\EnterNameType;
use App\Form\SelectSeasonType; use App\Form\SelectSeasonType;
use App\Helpers\Base64; use App\Helpers\Base64;
use Safe\Exceptions\UrlException; use App\Repository\CandidateRepository;
use App\Repository\QuestionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 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;
@@ -60,13 +63,23 @@ class QuizController extends AbstractController
name: 'quiz_page', name: 'quiz_page',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX], requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
)] )]
public function quizPage(Season $season, string $nameHash) public function quizPage(
{ Season $season,
try { string $nameHash,
$name = Base64::base64_url_decode($nameHash); CandidateRepository $candidateRepository,
} catch (UrlException $e) { QuestionRepository $questionRepository,
): Response {
$candidate = $candidateRepository->getCandidateByHash($season, $nameHash);
if (!$candidate instanceof Candidate) {
// Add option to add new candidate when preregister is disabled
$this->addFlash(FlashType::Danger->value, 'Candidate not found');
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]);
} }
return $this->render('quiz/question.twig', ['season' => $season, 'name' => $name]); $question = $questionRepository->findNextQuestionForCandidate($candidate);
return $this->render('quiz/question.twig', ['candidate' => $candidate, 'question' => $question]);
} }
} }

View File

@@ -34,8 +34,10 @@ class KrtekFixtures extends Fixture
->addCandidate(new Candidate('Philine')) ->addCandidate(new Candidate('Philine'))
->addCandidate(new Candidate('Remy')) ->addCandidate(new Candidate('Remy'))
->addCandidate(new Candidate('Robbert')) ->addCandidate(new Candidate('Robbert'))
->addCandidate(new Candidate('Tom')) ->addCandidate(new Candidate('Tom'));
->addQuiz($this->createQuiz1($season)) $quiz1 = $this->createQuiz1($season);
$season->addQuiz($quiz1)
->setActiveQuiz($quiz1)
->addQuiz($this->createQuiz2($season)); ->addQuiz($this->createQuiz2($season));
$manager->flush(); $manager->flush();
@@ -49,8 +51,8 @@ class KrtekFixtures extends Fixture
->addQuestion((new Question()) ->addQuestion((new Question())
->setQuestion('Is de Krtek een man of een vrouw?') ->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Ja', true)) ->addAnswer(new Answer('Vrouw', true))
->addAnswer(new Answer('Nee')) ->addAnswer(new Answer('Man'))
) )
->addQuestion((new Question()) ->addQuestion((new Question())

17
src/Enum/FlashType.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Enum;
enum FlashType: string
{
case Primary = 'primary';
case Secondary = 'secondary';
case Success = 'success';
case Danger = 'danger';
case Warning = 'warning';
case Info = 'info';
case Ligt = 'light';
case Dark = 'dark';
}

View File

@@ -14,12 +14,12 @@ class Base64
public static function base64_url_encode(string $input): string public static function base64_url_encode(string $input): string
{ {
return strtr(base64_encode($input), '+/=', '-_.'); return strtr(base64_encode($input), '+/', '-_');
} }
/** @throws UrlException */ /** @throws UrlException */
public static function base64_url_decode(string $input): string public static function base64_url_decode(string $input): string
{ {
return \Safe\base64_decode(strtr($input, '-_.', '+/='), true); return \Safe\base64_decode(strtr($input, '-_', '+/'), true);
} }
} }

View File

@@ -29,6 +29,11 @@ class CandidateRepository extends ServiceEntityRepository
return null; return null;
} }
return $this->findOneBy(['season' => $season, 'name' => $name]); return $this->createQueryBuilder('c')
->where('c.season = :season')
->andWhere('lower(c.name) = lower(:name)')
->setParameter('season', $season)
->setParameter('name', $name)
->getQuery()->getOneOrNullResult();
} }
} }

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Repository; namespace App\Repository;
use App\Entity\Candidate;
use App\Entity\GivenAnswer;
use App\Entity\Question; use App\Entity\Question;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
@@ -17,4 +19,25 @@ class QuestionRepository extends ServiceEntityRepository
{ {
parent::__construct($registry, Question::class); parent::__construct($registry, Question::class);
} }
public function findNextQuestionForCandidate(Candidate $candidate): Question
{
$qb = $this->createQueryBuilder('q');
return $qb->join('q.quiz', 'qz')
->andWhere($qb->expr()->notIn('q.id', $this->getEntityManager()->createQueryBuilder()
->select('ga.id')
->from(GivenAnswer::class, 'ga')
->join('ga.answer', 'a')
->join('a.question', 'q1')
->andWhere($qb->expr()->isNotNull('ga.answer'))
->andWhere('ga.candidate = :candidate')
->andWhere('q1.quiz = :quiz')
->getDQL()))
->andWhere('qz = :quiz')
->setMaxResults(1)
->setParameter('candidate', $candidate)
->setParameter('quiz', $candidate->getSeason()->getActiveQuiz())
->getQuery()->getSingleResult();
}
} }

View File

@@ -32,6 +32,14 @@
<body> <body>
<main> <main>
<div class="container"> <div class="container">
{% for label, messages in app.flashes() %}
{% for message in messages %}
<div class="alert alert-{{ label }} alert-dismissible " role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endfor %}
{% block body %} {% block body %}
{% endblock body %} {% endblock body %}
</div> </div>

View File

@@ -1,6 +1,9 @@
{% extends "quiz/base.html.twig" %} {% extends "quiz/base.html.twig" %}
{% block body %} {% block body %}
{{ season.name }} Candiadte: {{ candidate.name }}<br/>
{{ name }}
<h1>Hello World!</h1> {{ question.question }}<br/>
{% for answer in question.answers %}
<input type="radio" name="answer" value="{{ answer.id }}"> {{ answer.text }}
{% endfor %}
{% endblock body %} {% endblock body %}