feat(events): implement qr code check-in for event enrollments (#237)#298
Merged
danielhe4rt merged 9 commits intoJun 4, 2026
Merged
Conversation
… inscrição - Adiciona GenerateQrTokenAction: gera token único URL-safe de 64 chars por enrollment, idempotente — retorna token existente se já criado - Adiciona QrCheckInAction: valida token, enrollment e data do evento antes de delegar ao CheckInAction existente (method=qr_code, payload com token) - Adiciona QrCheckInDTO com validações de token e ator no construtor - Adiciona GenerateQrTokenOnConfirmed listener para o evento EnrollmentConfirmed - Adiciona exceções qrTokenNotFound e qrTokenExpired em CheckInException - Adiciona strings de tradução (en/pt_BR) para os novos erros de check-in QR
…erviceProvider Conecta EnrollmentConfirmed → GenerateQrTokenOnConfirmed para geração automática do token QR quando a inscrição é confirmada.
…ção de evento - Botão "Scan QR" no cabeçalho do EditEvent abre modal com campo de token - Executa QrCheckInAction e exibe notificação de sucesso com nome do participante - Em caso de erro (CheckInException ou Throwable), exibe notificação de erro - Modal reabre automaticamente via ->after() para leituras contínuas sem fechar - Botão de submit renomeado para "Check In" para evitar ambiguidade com confirmações
…sença hoje - EventDetail: adiciona computed qrToken (visível apenas com método QrCode e status confirmed/checked_in), qrCodeSvg via BaconQrCode, checkIns (histórico ordenado) e hasCheckedInToday para badge condicional - View: seção "Meu QR Code" com SVG inline, botões de copiar token e baixar SVG - Badge "Check-in feito hoje" no cabeçalho do card quando há check-in no dia atual - Seção de histórico de check-ins lista datas confirmadas abaixo do QR - Inscrição sem check-in ainda exibe instruções sem histórico - Adiciona strings de tradução en/pt_BR para histórico e badge
… QR e check-in Cobre todos os critérios de aceite da task he4rt#237: - Geração de token único por enrollment - Idempotência: segunda chamada retorna o mesmo token - Scan válido: cria CheckIn, atualiza status e dispara ParticipantCheckedIn - Token inexistente rejeitado - Token de outro evento rejeitado - Token expirado rejeitado - Scan duplicado no mesmo dia rejeitado - Token de enrollment cancelado rejeitado - Reutilização em dias diferentes cria check-ins distintos - Listener gera token automaticamente via EnrollUserAction (integração)
`mountAction` chamado em `->after()` era desfeito pelo `unmountAction` do lifecycle do Filament logo em seguida. A solução despacha um evento Livewire (`reopen-scan-qr`) dentro do `finally` da action, que é processado num request separado — após o lifecycle atual completar — e reabre o modal via `#[On]` sem conflito. Também adiciona `autofocus` no campo token para o cursor já estar posicionado a cada reabertura.
GabrielFVDev
approved these changes
Jun 2, 2026
GenerateQrTokenAction stored the raw Uuid object, leaving the in-memory token attribute as a LazyUuidFromString that broke QrCheckInDTO's string type hint. Persist it via Str::uuid7()->toString() instead. - Update QrCheckInActionTest to assert a valid UUID rather than a 64-char length. - Pass a waitlist position in EnrollmentStatusTest to match getResponseMessage(). - Drop unused DetachAction import in EnrollmentsRelationManager (Pint).
danielhe4rt
added a commit
that referenced
this pull request
Jun 4, 2026
…#298) Implements end-to-end QR code check-in for community events. When an enrollment reaches `confirmed` status, the system automatically generates a unique QR token. Organizers scan tokens via the admin panel to check participants in; participants view and share their QR code from the app panel. Closes #237 — blocked on #240 (RSVP enrollment, already merged). --- - `bacon/bacon-qr-code` — SVG QR generation via `BaconQrCode\Writer` + `SvgRenderer`. Chosen over `simplesoftwareio/simple-qrcode` due to PHP 8.4 GD extension incompatibility and Filament auto-discovery conflicts. --- | File | Role | | --- | --- | | `database/migrations/…create_events_qr_tokens_table.php` | `events_qr_tokens` table: `enrollment_id`, `token` (unique), `expires_at` (nullable) | | `src/CheckIn/Actions/GenerateQrTokenAction.php` | Creates token (64-char URL-safe hex via `bin2hex(random_bytes(32))`). Idempotent — skips if token already exists. | | `src/CheckIn/Actions/QrCheckInAction.php` | Validates token: exists, belongs to event enrollment, enrollment is `confirmed`/`checked_in`, not expired, not duplicate for today. Delegates to existing `CheckInAction` with `method=qr_code`. | | `src/CheckIn/DTOs/QrCheckInDTO.php` | Input DTO: `token`, `event_date` | | `src/CheckIn/Listeners/GenerateQrTokenOnConfirmed.php` | Listens to `EnrollmentConfirmed` domain event; dispatches `GenerateQrTokenAction`. Implements `ShouldDispatchAfterCommit` for transaction safety. | | `src/EventsServiceProvider.php` | Registers `EnrollmentConfirmed → GenerateQrTokenOnConfirmed` listener | | `src/CheckIn/Exceptions/CheckInException.php` | Adds `qrTokenNotFound`, `qrTokenExpired` factory methods | | `lang/en/check_in.php`, `lang/pt_BR/check_in.php` | i18n strings for new error states | --- - `EditEvent` resource: - Adds **Scan QR** header action with continuous-scan modal. - After each scan, the modal auto-reopens for rapid sequential check-ins. - Reopen is triggered via a Livewire event (`dispatch` + `#[On]`) so it runs in a separate request after Filament's `unmountAction` lifecycle completes. - Avoids calling `mountAction` directly inside `->after()` because Filament cleanup silently resets it. - Field uses `autofocus` so cursor lands on the token input every reopen. --- - `EventDetail` Livewire component: - Renders enrollment QR code as inline SVG when status is: - `confirmed` - `checked_in` - Adds: - copy-token button - download-SVG button - computed check-in history list - "present today" badge based on today's check-in records --- 10 feature tests covering all acceptance criteria: | Test | Scenario | | --- | --- | | `generates_qr_token_on_confirmed_enrollment` | Token created on `EnrollmentConfirmed` event | | `does_not_regenerate_existing_token` | Idempotency: second confirmation doesn't overwrite | | `valid_qr_scan_creates_check_in` | Happy path, `method=qr_code`, payload has token | | `dispatches_participant_checked_in_event` | Domain event fired on successful scan | | `rejects_unknown_token` | `qrTokenNotFound` exception | | `rejects_expired_token` | `expires_at < now()` → `qrTokenExpired` | | `rejects_duplicate_scan_same_day` | Already checked in today → `alreadyCheckedIn` | | `rejects_cancelled_enrollment_token` | Status check gates QR scan | | `same_token_creates_new_check_in_each_day` | Multi-day reuse creates separate records | | `listener_generates_token_after_commit` | `ShouldDispatchAfterCommit` respected in test | --- - [x] QR token is auto-generated when enrollment reaches `confirmed` status - [x] Token is unique and URL-safe - [x] Organizer can scan/input token and check participant in - [x] Same token works across multiple event days (creates new check-in per day) - [x] Expired token is rejected - [x] Duplicate scan on same day is rejected - [x] Cancelled enrollment's token is rejected on scan (status check) - [x] Participant sees their QR code in App panel - [x] Check-in record stores `method=qr_code` with token in payload - [x] `ParticipantCheckedIn` domain event dispatched - [x] Feature tests covering: - token generation - valid scan - expired token - duplicate scan - cancelled enrollment scan - multi-day reuse - [x] Pint passes --- ```bash make test vendor/bin/pest app-modules/events/tests/Feature/CheckIn/QrCheckInActionTest.php make pint ``` Results: - 74 tests passing - Pint clean --- | SHA | Message | | --- | --- | | `920f0c8` | `dep(events): adiciona biblioteca bacon/bacon-qr-code para geração de QR SVG` | | `1db9d28` | `feat(events/check-in): implementa geração e validação de token QR por inscrição` | | `1bd4eb1` | `feat(events): registra listener GenerateQrTokenOnConfirmed no EventsServiceProvider` | | `22b920f` | `feat(panel-admin): adiciona ação de scan QR contínuo na página de edição de evento` | | `d6be299` | `feat(panel-app): exibe QR code, histórico de check-ins e badge de presença hoje` | | `ca9da88` | `test(events/check-in): adiciona suite de testes para geração de token QR e check-in` | | `1607145` | `fix(panel-admin): corrige reabertura contínua do modal de scan QR` | --------- Co-authored-by: Daniel Reis <danielhe4rt@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements end-to-end QR code check-in for community events.
When an enrollment reaches
confirmedstatus, the system automatically generates a unique QR token. Organizers scan tokens via the admin panel to check participants in; participants view and share their QR code from the app panel.Closes #237 — blocked on #240 (RSVP enrollment, already merged).
What changed
New dependency
bacon/bacon-qr-code— SVG QR generation viaBaconQrCode\Writer+SvgRenderer.Chosen over
simplesoftwareio/simple-qrcodedue to PHP 8.4 GD extension incompatibility and Filament auto-discovery conflicts.Core —
app-modules/eventsdatabase/migrations/…create_events_qr_tokens_table.phpevents_qr_tokenstable:enrollment_id,token(unique),expires_at(nullable)src/CheckIn/Actions/GenerateQrTokenAction.phpbin2hex(random_bytes(32))). Idempotent — skips if token already exists.src/CheckIn/Actions/QrCheckInAction.phpconfirmed/checked_in, not expired, not duplicate for today. Delegates to existingCheckInActionwithmethod=qr_code.src/CheckIn/DTOs/QrCheckInDTO.phptoken,event_datesrc/CheckIn/Listeners/GenerateQrTokenOnConfirmed.phpEnrollmentConfirmeddomain event; dispatchesGenerateQrTokenAction. ImplementsShouldDispatchAfterCommitfor transaction safety.src/EventsServiceProvider.phpEnrollmentConfirmed → GenerateQrTokenOnConfirmedlistenersrc/CheckIn/Exceptions/CheckInException.phpqrTokenNotFound,qrTokenExpiredfactory methodslang/en/check_in.php,lang/pt_BR/check_in.phpAdmin panel —
app-modules/panel-adminEditEventresource:dispatch+#[On]) so it runs in a separate request after Filament'sunmountActionlifecycle completes.mountActiondirectly inside->after()because Filament cleanup silently resets it.autofocusso cursor lands on the token input every reopen.App panel —
app-modules/panel-appEventDetailLivewire component:confirmedchecked_inTests —
app-modules/events/tests10 feature tests covering all acceptance criteria:
generates_qr_token_on_confirmed_enrollmentEnrollmentConfirmedeventdoes_not_regenerate_existing_tokenvalid_qr_scan_creates_check_inmethod=qr_code, payload has tokendispatches_participant_checked_in_eventrejects_unknown_tokenqrTokenNotFoundexceptionrejects_expired_tokenexpires_at < now()→qrTokenExpiredrejects_duplicate_scan_same_dayalreadyCheckedInrejects_cancelled_enrollment_tokensame_token_creates_new_check_in_each_daylistener_generates_token_after_commitShouldDispatchAfterCommitrespected in testAcceptance criteria
confirmedstatusmethod=qr_codewith token in payloadParticipantCheckedIndomain event dispatchedTest plan
Results:
Commits
920f0c8dep(events): adiciona biblioteca bacon/bacon-qr-code para geração de QR SVG1db9d28feat(events/check-in): implementa geração e validação de token QR por inscrição1bd4eb1feat(events): registra listener GenerateQrTokenOnConfirmed no EventsServiceProvider22b920ffeat(panel-admin): adiciona ação de scan QR contínuo na página de edição de eventod6be299feat(panel-app): exibe QR code, histórico de check-ins e badge de presença hojeca9da88test(events/check-in): adiciona suite de testes para geração de token QR e check-in1607145fix(panel-admin): corrige reabertura contínua do modal de scan QR