Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
390b745
feat: new navbar, home, footer for 2026
pedroafmonteiro Sep 1, 2026
e6497d6
chore: made the glowing orbs random
pedroafmonteiro Sep 1, 2026
3c6ebe8
feat: program page
pedroafmonteiro Sep 1, 2026
8d0ad99
changed background color to almost black
pedroafmonteiro Sep 1, 2026
4f52d2c
feat: shop page done
pedroafmonteiro Sep 1, 2026
19c7b37
feat: team page
pedroafmonteiro Sep 1, 2026
c038aca
feat: competition pages
pedroafmonteiro Sep 1, 2026
68189f9
feat: event pages
pedroafmonteiro Sep 1, 2026
ce2b21e
feat: auth pages
pedroafmonteiro Sep 3, 2026
ac8862f
chore: standardized a few components for consistency
pedroafmonteiro Sep 3, 2026
9a64c52
feat: improved auth pages with better layout and components
pedroafmonteiro Sep 3, 2026
9aa771c
feat: edit profile page
pedroafmonteiro Sep 3, 2026
79b6131
fix: profile picture in navbar
pedroafmonteiro Sep 4, 2026
4b35838
feat: better dropdowns
pedroafmonteiro Sep 4, 2026
26869c2
fix: seeder and competitions/talks stats
pedroafmonteiro Sep 4, 2026
c95e364
feat: profile page
pedroafmonteiro Sep 7, 2026
8716c87
feat: scan code page and fixes
pedroafmonteiro Sep 7, 2026
25873c0
fix: increased max width
pedroafmonteiro Sep 7, 2026
c67d6e8
fix: layout
pedroafmonteiro Sep 7, 2026
9a157aa
fix: better team page
pedroafmonteiro Sep 7, 2026
b0b5c42
feat: better program page
pedroafmonteiro Sep 7, 2026
430b608
fix: competition page layout
pedroafmonteiro Sep 7, 2026
dfb6bd7
feat: better event page
pedroafmonteiro Sep 7, 2026
422debc
fix: shop page layout
pedroafmonteiro Sep 7, 2026
3c13c89
old editions toggable
pedroafmonteiro Sep 7, 2026
16957b6
chore: refactored folders
pedroafmonteiro Sep 12, 2026
edda3e2
chore: refactored folders
pedroafmonteiro Sep 12, 2026
085859c
feat: admin dashboard
pedroafmonteiro Sep 12, 2026
6ba1cec
feat: landing page
pedroafmonteiro Sep 13, 2026
08893d6
fix: inconsitencies
pedroafmonteiro Sep 13, 2026
26e02c7
formatted and linted
pedroafmonteiro Sep 13, 2026
e00fdd4
feat: made banner work
pedroafmonteiro Sep 13, 2026
3e38418
chore: fix code style
peucastro Oct 6, 2026
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
63 changes: 63 additions & 0 deletions app/Http/Controllers/AdminDashboardController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace App\Http\Controllers;

use App\Models\Competition;
use App\Models\Edition;
use App\Models\Enrollment;
use App\Models\Event;
use App\Models\EventDay;
use App\Models\Product;
use App\Models\Quest;
use App\Models\Sponsor;
use App\Models\Stand;
use App\Models\User;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class AdminDashboardController extends Controller
{
public function __invoke(Request $request): Response
{
$currentEdition = Edition::orderByDesc('year')->first();

$stats = [
'users_count' => User::count(),
'enrollments_count' => $currentEdition ? $currentEdition->enrollments()->count() : Enrollment::count(),
'events_count' => $currentEdition ? $currentEdition->events()->count() : Event::count(),
'event_days_count' => $currentEdition ? $currentEdition->event_days()->count() : EventDay::count(),
'sponsors_count' => $currentEdition ? Sponsor::where('edition_id', $currentEdition->id)->count() : Sponsor::count(),
'stands_count' => Stand::count(),
'products_count' => Product::count(),
'quests_count' => Quest::count(),
'competitions_count' => $currentEdition ? Competition::where('edition_id', $currentEdition->id)->count() : Competition::count(),
];

$upcomingEvents = Event::with(['event_day', 'type'])
->orderBy('event_day_id')
->orderBy('time_start')
->limit(5)
->get();

$recentEnrollments = Enrollment::with(['participant.user', 'edition'])
->latest()
->limit(5)
->get()
->map(fn ($enrollment) => [
'id' => $enrollment->id,
'name' => $enrollment->participant?->user?->name ?? 'Utilizador Desconhecido',
'email' => $enrollment->participant?->user?->email ?? '-',
'edition' => $enrollment->edition?->name ?? '-',
'points' => $enrollment->points,
'created_at' => $enrollment->created_at?->format('d/m/Y H:i') ?? '-',
]);

return Inertia::render('Admin', [
'currentEdition' => $currentEdition,
'stats' => $stats,
'upcomingEvents' => $upcomingEvents,
'recentEnrollments' => $recentEnrollments,
]);
}
}
104 changes: 66 additions & 38 deletions app/Http/Controllers/CRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,65 +71,97 @@ protected function with(): array
*/
protected $load = [];

public function index(Request $request)
/**
* Common method to get paginated items, filters, search and relations.
*
* @return array<string, mixed>
*/
protected function getIndexData(Request $request): array
{
$isSearchable = in_array(Searchable::class, class_uses($this->model));

$search = $request->query('query');
if ($isSearchable && $search !== null) {
$query = $this->model::search($search)->query(fn (Builder $query) => $query->with($this->load));
$sortBy = $request->query('sort_by', 'id');
$sortDir = strtolower($request->query('sort_dir', 'asc')) === 'desc' ? 'desc' : 'asc';

$modelInstance = new $this->model;
$table = $modelInstance->getTable();
$schema = $modelInstance->getConnection()->getSchemaBuilder();

$applySortAndFilters = function (Builder $query) use ($table, $schema, $sortBy, $sortDir, $request) {
$reserved = ['page', 'query', 'sort_by', 'sort_dir', 'filter_by', 'filter_val'];
foreach ($request->query() as $key => $value) {
if (! in_array($key, $reserved) && $value !== null && $value !== '') {
if ($schema->hasColumn($table, $key)) {
$query->where($table.'.'.$key, $value);
}
}
}

if ($request->filled('filter_by') && $request->has('filter_val') && $request->get('filter_val') !== '') {
$filterCol = $request->query('filter_by');
if ($schema->hasColumn($table, $filterCol)) {
$query->where($table.'.'.$filterCol, $request->query('filter_val'));
}
}

if ($schema->hasColumn($table, $sortBy)) {
$query->orderBy($table.'.'.$sortBy, $sortDir);
} else {
$query->orderBy($table.'.id', 'asc');
}
};

if ($isSearchable && $search !== null && trim($search) !== '') {
$query = $this->model::search($search)->query(function (Builder $query) use ($applySortAndFilters) {
$query->with($this->load);
$applySortAndFilters($query);
});
} else {
$query = $this->model::with($this->load)->orderBy('id');
$query = $this->model::with($this->load);
$applySortAndFilters($query);
}

$filteredQuery = collect($request->query())
->intersectByKeys(['sort_by' => '', 'sort_dir' => '', 'query' => '', 'filter_by' => '']);
$items = $query->paginate()->appends($filteredQuery->toArray());
$items = $query->paginate()->appends($request->query());

$with = $this->with();

Log::info('Displaying all {model} records with query: {search}', ['model' => $this->model, 'search' => $search ?? 'none']);

return Inertia::render("CRUD/{$this->view}/Index", [
return [
'items' => $items,
'with' => $with,
'with' => $this->with(),
'isSearchable' => $isSearchable,
]);
'view' => $this->view,
];
}

public function show($id)
public function index(Request $request)
{
$item = $this->model::find($id);
$data = $this->getIndexData($request);

$with = $this->with();
Log::info('Displaying all {model} records with query: {search}', ['model' => $this->model, 'search' => $request->query('query') ?? 'none']);

Log::info('Displaying {model} record with id: {id}', ['model' => $this->model, 'id' => $id]);
return Inertia::render("CRUD/{$this->view}/Index", $data);
}

return Inertia::render("CRUD/$this->view/Show", [
'item' => $item,
'with' => $with,
]);
public function show(Request $request, $id)
{
return $this->edit($request, $id);
}

public function create()
public function create(Request $request)
{
$with = $this->with();
$data = $this->getIndexData($request);
$data['modal'] = 'create';

return Inertia::render("CRUD/$this->view/Create", [
'with' => $with,
]);
return Inertia::render("CRUD/{$this->view}/Index", $data);
}

public function edit($id)
public function edit(Request $request, $id)
{
$data = $this->getIndexData($request);
$item = $this->model::find($id);
$data['modal'] = 'edit';
$data['item'] = $item ? $item->load($this->load) : null;

$with = $this->with();

return Inertia::render("CRUD/$this->view/Edit", [
'item' => $item->load($this->load),
'with' => $with,
]);
return Inertia::render("CRUD/{$this->view}/Index", $data);
}

/**
Expand All @@ -147,13 +179,11 @@ protected function created(array $new): ?array

public function store(Request $request)
{

$validated = $request->validate($this->storeRules());

$newValues = $this->created($validated);

if ($newValues !== null) {

Log::info('Creating new {model} record with values: {values}', ['model' => $this->model, 'values' => Json::encode($newValues, true)]);

$this->model::create($newValues);
Expand Down Expand Up @@ -185,7 +215,6 @@ public function update(Request $request, $id)
$newValues = $this->updated($model, $validated);

if ($newValues !== null) {

Log::info('Updating {model} record with id {id} with values: {values}', ['model' => $this->model, 'id' => $model->id, 'values' => Json::encode($newValues, true)]);

$model->update($newValues);
Expand All @@ -211,7 +240,6 @@ public function destroy($id)
$model = $this->model::find($id);

if ($this->destroyed($model->toArray())) {

Log::alert('Deleting {model} record with id {id}', ['model' => $this->model, 'id' => $model->id]);

$model->delete();
Expand Down
80 changes: 25 additions & 55 deletions app/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;

class EventController extends Controller
Expand Down Expand Up @@ -40,7 +39,7 @@ public function show(Request $request, Event $event)
}

return Inertia::render('Event', [
'event' => $event->load(['users', 'event_day']),
'event' => $event->load(['users', 'event_day', 'type']),
'enrollments' => $enrollments,
'enrollmentCount' => $enrollmentCount,
'isParticipant' => $isParticipant,
Expand All @@ -56,79 +55,50 @@ public function show(Request $request, Event $event)
*/
public function join(Request $request, Event $event)
{

$edition = $request->input('edition');

if ($edition === null) {
return response('No edition found', 500);
}

/** @var User|null */
/** @var User */
$user = $request->user();

if ($user === null) {
return redirect()->back()->dangerBanner('Inicia sessão para fazer esta ação');
}

if ($user->cannot('join', $event)) {
Log::alert('User {user} attempted to join event "{event}" but was denied', [
'user' => $user->name,
'event' => $event->name,
]);

return redirect()->back()->dangerBanner('Não podes inscrever-te neste evento');
return redirect()->route('login');
}

$currentEnrollment = $user->usertype->enrollments()->where('edition_id', $edition->id)->first(); // we can safely get only the first one because there should only be one.

if ($currentEnrollment === null) {
Log::alert('User {user} attempted to join event "{event}" while not enrolled in the current edition', [
'user' => $user->name,
'event' => $event->name,
]);
if ($user->can('join', $event)) {
$editionId = $request->input('edition')?->id ?? $event->event_day?->edition_id;
$enrollment = $editionId
? $user->usertype->enrollments()->where('edition_id', $editionId)->first()
: $user->usertype->enrollments()->latest()->first();

return redirect()->route('home')->dangerBanner('Não estás inscrito nesta edição!');
if ($enrollment) {
$enrollment->events()->syncWithoutDetaching([$event->id]);
}
}

$currentEnrollment->events()->attach($event);
Log::info('User {user} joined event {event}', [
'user' => $user->name,
'event' => $event->name,
]);

return redirect()->route('profile.show')->banner('Inscrição realizada com sucesso!');
return redirect()->back();
}

/**
* The current user wants to leave the given event if enrolled.
* The current user wants to leave the given event.
*/
public function leave(Request $request, Event $event)
{
/** @var User */
$user = $request->user();

$edition = $request->input('edition');

if ($edition === null) {
return response('No edition found', 500);
if ($user === null) {
return redirect()->route('login');
}

$currentEnrollment = $user->usertype->enrollments()->where('edition_id', $edition->id)->first(); // we can safely get only the first one because there should only be one.

if ($currentEnrollment === null) {
Log::alert('User {user} attempted to leave event "{event}" while not enrolled in the current edition', [
'user' => $user->name,
'event' => $event->name,
]);
if ($user->can('leave', $event)) {
$editionId = $request->input('edition')?->id ?? $event->event_day?->edition_id;
$enrollment = $editionId
? $user->usertype->enrollments()->where('edition_id', $editionId)->first()
: $user->usertype->enrollments()->latest()->first();

return redirect()->route('home')->dangerBanner('Não estás inscrito nesta edição!');
if ($enrollment) {
$enrollment->events()->detach($event->id);
}
}

$currentEnrollment->events()->detach($event);
Log::info('User {user} left event "{event}"', [
'user' => $user->name,
'event' => $event->name,
]);

return redirect()->route('profile.show')->banner('Inscrição cancelada com sucesso!');
return redirect()->back();
}
}
2 changes: 2 additions & 0 deletions app/Http/Controllers/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function show(Request $request)
// $activity_count = $edition->workshops()->count();
$talk_count = $edition->events()->talk()->count();
$activity_count = $edition->events()->activity()->count();
$competition_count = $edition->competitions()->count();

$stand_count = $edition->stands()->count();

Expand All @@ -38,6 +39,7 @@ public function show(Request $request)
'speakers' => $speakers,
'activityCount' => $activity_count,
'talkCount' => $talk_count,
'competitionCount' => $competition_count,
'days' => $days,
'standCount' => $stand_count,
'canEnroll' => $can_enroll,
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Controllers/ProgramController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function show(Request $request)

if ($totalDays === 0) {
return Inertia::render('Program', [
'days' => fn () => [],
'eventDay' => fn () => null,
'queryDay' => fn () => 0,
'totalDays' => fn () => 0,
Expand All @@ -55,6 +56,7 @@ public function show(Request $request)
$eventDay?->competitions;

return Inertia::render('Program', [
'days' => fn () => $edition->event_days()->orderBy('date', 'ASC')->get(['id', 'date', 'theme']),
'eventDay' => fn () => $eventDay?->load([
'stands' => [
'sponsor' => [
Expand Down
Loading
Loading