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;
use App\Entity\Candidate;
use App\Entity\Season;
use App\Enum\FlashType;
use App\Form\EnterNameType;
use App\Form\SelectSeasonType;
use App\Helpers\Base64;
use Safe\Exceptions\UrlException;
use App\Repository\CandidateRepository;
use App\Repository\QuestionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -60,13 +63,23 @@ class QuizController extends AbstractController
name: 'quiz_page',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
)]
public function quizPage(Season $season, string $nameHash)
{
try {
$name = Base64::base64_url_decode($nameHash);
} catch (UrlException $e) {
public function quizPage(
Season $season,
string $nameHash,
CandidateRepository $candidateRepository,
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('Remy'))
->addCandidate(new Candidate('Robbert'))
->addCandidate(new Candidate('Tom'))
->addQuiz($this->createQuiz1($season))
->addCandidate(new Candidate('Tom'));
$quiz1 = $this->createQuiz1($season);
$season->addQuiz($quiz1)
->setActiveQuiz($quiz1)
->addQuiz($this->createQuiz2($season));
$manager->flush();
@@ -49,8 +51,8 @@ class KrtekFixtures extends Fixture
->addQuestion((new Question())
->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Ja', true))
->addAnswer(new Answer('Nee'))
->addAnswer(new Answer('Vrouw', true))
->addAnswer(new Answer('Man'))
)
->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
{
return strtr(base64_encode($input), '+/=', '-_.');
return strtr(base64_encode($input), '+/', '-_');
}
/** @throws UrlException */
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 $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;
use App\Entity\Candidate;
use App\Entity\GivenAnswer;
use App\Entity\Question;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -17,4 +19,25 @@ class QuestionRepository extends ServiceEntityRepository
{
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>
<main>
<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 %}
{% endblock body %}
</div>

View File

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