Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - QR codes/ticket system #537

Merged
merged 24 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1e1bef4
Add qrcode package
IGORnvk Sep 12, 2024
fdfa0e4
Add ticket functionality
IGORnvk Sep 12, 2024
c4b7143
Fix typo
IGORnvk Sep 13, 2024
03e5317
Protect scanning route with policy and crew middleware
IGORnvk Sep 13, 2024
22e5858
Fix phpcs
IGORnvk Sep 13, 2024
9eb3add
Add attendee name and role to scanning page
IGORnvk Sep 13, 2024
2c345f5
Add a check if user is crew before sending ticket
IGORnvk Sep 13, 2024
d5253a1
Add ticket status to moderator's users page
IGORnvk Sep 14, 2024
f1ce337
Minor refactoring
IGORnvk Sep 14, 2024
92faa30
Add page for scanning tickets by the crew
IGORnvk Sep 14, 2024
3d5c706
Fix condition
IGORnvk Sep 14, 2024
e6f8f53
Fix the sidebar on small screens
IGORnvk Sep 15, 2024
404ff79
Make scanner responsive
IGORnvk Sep 15, 2024
78f7e81
Merge branch 'development' into feature/qr-codes
IGORnvk Sep 16, 2024
7464e85
Add function docs and remove unused logic
IGORnvk Sep 16, 2024
6a3431e
Merge branch 'feature/qr-codes' of https://github.com/HZ-HBO-ICT/it-c…
IGORnvk Sep 16, 2024
380bc98
Add ticket to user's profile page
IGORnvk Sep 16, 2024
c743e52
Implement ticket in `final programme released` logic
IGORnvk Sep 17, 2024
612fc53
Minor refactoring
IGORnvk Sep 17, 2024
199ef8f
Merge branch 'development' into feature/qr-codes
IGORnvk Sep 17, 2024
3304e93
Fix build issues
IGORnvk Sep 17, 2024
3ef45b6
Restrict sending tickets to crew
IGORnvk Sep 18, 2024
67e0f71
Add command to create tickets for verified users
IGORnvk Sep 19, 2024
35334fe
Merge branch 'development' into feature/qr-codes
TimKardol Oct 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/Http/Controllers/Crew/TicketController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Http\Controllers\Crew;

use App\Http\Controllers\Controller;
use App\Models\Ticket;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class TicketController extends Controller
{
/**
* Render index page
*
* @return View
*/
public function index(): View
{
if (Auth::user()->cannot('scan', Ticket::class)) {
abort(403);
}

return view('crew.tickets.index');
}
}
2 changes: 1 addition & 1 deletion app/Http/Middleware/EditionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function handle(Request $request, Closure $next): Response

if ($user->is_crew) {
return redirect(route('dashboard'))
->dangetButton("There is no active edition. You can't access this page for now.");
->dangerButton("There is no active edition. You can't access this page for now.");
}
}

Expand Down
51 changes: 51 additions & 0 deletions app/Listeners/EmailVerifiedListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Listeners;

use App\Mail\TicketMailable;
use App\Models\Ticket;
use Illuminate\Auth\Events\Verified;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use SimpleSoftwareIO\QrCode\Facades\QrCode;

class EmailVerifiedListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}

/**
* Handle the event.
*/
public function handle(Verified $event): void
{
$user = $event->user;

// Crew members do not need tickets
if ($user->is_crew) {
return;
}

// Generate unique identifier for the ticket
$ticketToken = Str::uuid();

// Create new tickets
$ticket = new Ticket();
$ticket->user_id = $user->id;
$ticket->token = $ticketToken;
$ticket->save();

// Generate qr code
$qrCode = $user->generateTicket();

// Send email to the user with qr code
Mail::to($user->email)->send(new TicketMailable($qrCode));
}
}
93 changes: 93 additions & 0 deletions app/Livewire/QrCode/InfoModal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace App\Livewire\QrCode;

use App\Models\Ticket;
use App\Models\User;
use Illuminate\View\View;
use LivewireUI\Modal\ModalComponent;

class InfoModal extends ModalComponent
{
public User $user;
public string $message;
public string $color;

/**
* Initialize the modal
*
* @param array $data
* @return void
*/
public function mount(array $data): void
{
if (!isset($data['id']) || !isset($data['token'])) {
$this->message = 'Invalid data';
$this->color = 'red';

return;
}

$this->user = User::find($data['id']);

$ticket = Ticket::where([
'user_id' => $data['id'],
'token' => $data['token']
])->first();

if ($this->isTicketValid($ticket)) {
$this->message = 'Success';
$this->color = 'green';

$ticket->scanned_at = now();
$ticket->save();
} else {
$this->color = 'red';
}
}

/**
* Determine whether the given ticket is valid
*
* @param Ticket $ticket
* @return bool
*/
public function isTicketValid(Ticket $ticket): bool
{
if (!$this->user->id) {
$this->message = 'Person does not exist';
return false;
}

if (!$ticket->id) {
$this->message = 'Ticket does not exist';
return false;
}

if ($ticket->scanned_at) {
$this->message = 'Ticket was already scanned';
return false;
}

return true;
}

/**
* Sets the maximum width of the modal according to docs
* @return string
*/
public static function modalMaxWidth(): string
{
return 'md';
}

/**
* Render the modal
*
* @return View
*/
public function render(): View
{
return view('livewire.qr-code.info-modal');
}
}
20 changes: 20 additions & 0 deletions app/Livewire/QrCode/Scanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Livewire\QrCode;

use Illuminate\View\View;
use LivewireUI\Modal\ModalComponent;

class Scanner extends ModalComponent
{
/**
* Render the component
*
* @return View
*/
public function render(): View
{
$this->dispatch('enableScanner');
return view('livewire.qr-code.scanner');
}
}
20 changes: 20 additions & 0 deletions app/Livewire/QrCode/Ticket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Livewire\QrCode;

use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use Livewire\Component;

class Ticket extends Component
{
/**
* Renders the component
*
* @return View
*/
public function render(): View
{
return view('livewire.qr-code.ticket', ['ticket' => Auth::user()->generateTicket()]);
}
}
11 changes: 6 additions & 5 deletions app/Livewire/Users/UserFilteringList.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class UserFilteringList extends Component
*/
public function mount()
{
$this->users = User::all()->sortBy('name');
$this->filter();
}

Expand Down Expand Up @@ -62,16 +61,16 @@ public function updatedEmail()
*/
public function filter()
{
$this->users = User::all()->sortBy('name');
$this->users = User::usersWithTickets()->get();

if ($this->role) {
if ($this->role == 'speaker') {
$this->users = User::all()->filter(function ($user) {
$this->users = $this->users->filter(function ($user) {
$roles = $user->allRoles;
return $roles->contains('speaker');
});
} else {
$this->users = User::role($this->role)->get()->sortBy('name');
$this->users = User::role($this->role)->usersWithTickets()->get();
}
}

Expand Down Expand Up @@ -105,7 +104,7 @@ public function clearFilters()
$this->institution = '';
$this->role = '';

$this->users = User::all()->sortBy('name');
$this->users = User::usersWithTickets()->get();
}

/**
Expand Down Expand Up @@ -135,6 +134,7 @@ public function export()
'Phone Number',
'Institution/Company',
'Roles',
'Ticket Status',
]);

// Fetch and process data in chunks
Expand All @@ -146,6 +146,7 @@ public function export()
$user->company && $user->company->phone_number ? $user->company->phone_number : '',
$user->company ? $user->company->name : $user->institution,
isset($user->all_roles) ? implode(", ", json_decode($user->all_roles)) : '',
$user->ticket_status['status'],
];

// Write data to a CSV file.
Expand Down
54 changes: 54 additions & 0 deletions app/Mail/TicketMailable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\HtmlString;

class TicketMailable extends Mailable
{
use Queueable, SerializesModels;

/**
* Create a new message instance.
*/
public function __construct(
public HtmlString $qrCode
) {
}

/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Your ticket for We are in IT together Conference',
);
}

/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.ticket',
);
}

/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
28 changes: 28 additions & 0 deletions app/Models/Ticket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Ticket extends Model
{
use HasFactory;

protected $fillable = [
'user_id',
'token',
'scanned_at',
];

/**
* Establish relationship with User
*
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Loading
Loading