Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
228 changes: 127 additions & 101 deletions .phpstorm.meta.php

Large diffs are not rendered by default.

288 changes: 227 additions & 61 deletions _ide_helper.php

Large diffs are not rendered by default.

56 changes: 36 additions & 20 deletions app/Livewire/AvatarSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Traits\WithProfilePhoto;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
use Mary\Traits\Toast;

Expand Down Expand Up @@ -81,19 +82,28 @@ public function getAvatarOptionsProperty()
*/
public function updatedPhoto()
{
// Only validate the photo, don't save yet
$this->validate(
['photo' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:10240']],
[
'photo.image' => 'The file must be an image.',
'photo.mimes' => 'The image must be a JPG, PNG, or WebP file.',
'photo.max' => 'The image must not exceed 10MB.',
]
);
// Photo preview will be shown automatically via $photo->temporaryUrl()

// Dispatch event to trigger avatar re-initialization
$this->dispatch('photo-uploaded');
try {
// Only validate the photo, don't save yet
$this->validate(
['photo' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:5120']],
[
'photo.image' => 'The file must be an image.',
'photo.mimes' => 'The image must be a JPG, PNG, or WebP file.',
'photo.max' => 'The image must not exceed 5MB.',
]
);
// Photo preview will be shown automatically via $photo->temporaryUrl()

// Dispatch event to trigger avatar re-initialization
$this->dispatch('photo-uploaded');
} catch (\Illuminate\Validation\ValidationException $e) {
$this->photo = null;
throw $e; // Re-throw to let Livewire handle validation display
} catch (\Exception $e) {
Log::error('Photo upload failed: ' . $e->getMessage());
$this->photo = null;
$this->error('The photo failed to upload. Please try again with a smaller file.', position: 'toast-top');
}
}

/**
Expand All @@ -109,14 +119,20 @@ public function cancelPhotoUpload()
*/
public function saveUploadedPhoto()
{
if (!$this->photo) {
$this->error('No photo to save.', position: 'toast-top');
return;
}
try {
if (!$this->photo) {
$this->error('No photo to save.', position: 'toast-top');
return;
}

$this->saveProfilePhoto();
$this->dispatch('avatar-updated');
$this->js('window.dispatchEvent(new CustomEvent("avatar-changed"))');
$this->saveProfilePhoto();
$this->dispatch('avatar-updated');
$this->js('window.dispatchEvent(new CustomEvent("avatar-changed"))');
} catch (\Exception $e) {
Log::error('Failed to save profile photo: ' . $e->getMessage());
$this->photo = null;
$this->error('Failed to save the photo. Please try again.', position: 'toast-top');
}
}

/**
Expand Down
21 changes: 11 additions & 10 deletions app/Livewire/Components/EventCalendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,12 @@ public function eventsForCalendar()
])
// Always show only approved event schedules
->where('status', 'approved')
// Filter by ticket status - empty means show both approved and rescheduled
// Filter by ticket status - 'all' or empty means show both approved and rescheduled
->whereHas('event.ticket', function ($query) {
if ($this->statusFilter) {
if ($this->statusFilter && $this->statusFilter !== 'all') {
$query->where('status', $this->statusFilter);
} else {
// Show both approved and rescheduled when no specific filter
// Show both approved and rescheduled when 'all' or no specific filter
$query->whereIn('status', ['approved', 'rescheduled']);
}
})
Expand Down Expand Up @@ -336,7 +336,8 @@ public function eventsForCalendar()
'start' => $startISO,
'end' => $endISO,
'allDay' => false,
'backgroundColor' => $this->getEventColor($event), // 50% opacity
'display' => 'block', // Show as solid bar instead of dot
'backgroundColor' => $this->getEventColor($event),
'textColor' => '#ffffff',
'extendedProps' => [
'organization' => $event->ticket->user->studentOrganization->org_name ?? 'No Organization',
Expand Down Expand Up @@ -492,12 +493,12 @@ public function uniqueEventsCount()
{
// Count unique events that match the current filters
$query = Event_Schedule::query()
// Filter by ticket status - empty means show both approved and rescheduled
// Filter by ticket status - 'all' or empty means show both approved and rescheduled
->whereHas('event.ticket', function ($query) {
if ($this->statusFilter) {
if ($this->statusFilter && $this->statusFilter !== 'all') {
$query->where('status', $this->statusFilter);
} else {
// Show both approved and rescheduled when no specific filter
// Show both approved and rescheduled when 'all' or no specific filter
$query->whereIn('status', ['approved', 'rescheduled']);
}
})
Expand Down Expand Up @@ -578,13 +579,13 @@ public function upcomingEventsThisMonth()
$hexColor = $this->getEventColor($event);
$colorName = $this->hexToTailwindColor($hexColor);

$orgLogo = $event->ticket->user->studentOrganization->logo ?? null;
$org = $event->ticket->user->studentOrganization ?? null;

return [
'title' => $event->ticket->title ?? 'Untitled Event',
'description' => $event->ticket->description ?? null,
'organization' => $event->ticket->user->studentOrganization->org_name ?? 'No Organization',
'organizationLogo' => $orgLogo ? $orgLogo->logo_url : asset('images/default-org-logo.svg'),
'organization' => $org->org_name ?? 'No Organization',
'organizationLogo' => $org ? $org->logo_url : asset('images/default-org-logo.svg'),
'eventType' => $event->eventType?->type_name ?? 'N/A',
'date' => $dateDisplay,
'time' => $timeRange,
Expand Down
34 changes: 34 additions & 0 deletions app/Livewire/Faq.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Livewire;

use App\Models\Faq as FaqModel;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('components.layouts.public')]
#[Title('FAQ - Frequently Asked Questions')]
class Faq extends Component
{
/**
* Get all active FAQs grouped by category
*/
public function getFaqsProperty()
{
return FaqModel::getActiveGroupedByCategory();
}

/**
* Check if there are any FAQs available
*/
public function getHasFaqsProperty(): bool
{
return FaqModel::active()->exists();
}

public function render()
{
return view('livewire.faq');
}
}
94 changes: 58 additions & 36 deletions app/Livewire/Gso/Notifications.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace App\Livewire\Gso;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
Expand Down Expand Up @@ -123,63 +125,83 @@ public function getNotificationsProperty()

public function markAsRead($notificationId)
{
$user = auth()->user();
$notification = $user->notifications()->find($notificationId);

if ($notification) {
$notification->markAsRead();
// Clear cache to refresh counts
\Illuminate\Support\Facades\Cache::forget("osa_notifications_counts_{$user->user_id}");
$this->loadCounts();
try {
$user = auth()->user();
$notification = $user->notifications()->find($notificationId);

if ($notification) {
$notification->markAsRead();
// Clear cache to refresh counts
Cache::forget("gso_notifications_counts_{$user->user_id}");
$this->loadCounts();
}
} catch (\Exception $e) {
Log::error('Failed to mark notification as read: ' . $e->getMessage());
$this->error('Failed to mark notification as read.', position: 'toast-top');
}
}

public function markAllAsRead()
{
$user = auth()->user();
$user->unreadNotifications->markAsRead();
// Clear cache to refresh counts
\Illuminate\Support\Facades\Cache::forget("osa_notifications_counts_{$user->user_id}");
$this->loadCounts();
try {
$user = auth()->user();
$user->unreadNotifications->markAsRead();
// Clear cache to refresh counts
Cache::forget("gso_notifications_counts_{$user->user_id}");
$this->loadCounts();

$this->success('All notifications marked as read.', position: 'toast-top');
$this->success('All notifications marked as read.', position: 'toast-top');
} catch (\Exception $e) {
Log::error('Failed to mark all notifications as read: ' . $e->getMessage());
$this->error('Failed to mark all notifications as read.', position: 'toast-top');
}
}

public function deleteNotification($notificationId)
{
$user = auth()->user();
$notification = $user->notifications()->find($notificationId);
try {
$user = auth()->user();
$notification = $user->notifications()->find($notificationId);

if ($notification) {
$notification->delete();
if ($notification) {
$notification->delete();

// Clear cache to refresh counts
\Illuminate\Support\Facades\Cache::forget("osa_notifications_counts_{$user->user_id}");
$this->loadCounts();
$this->resetPage();
// Clear cache to refresh counts
Cache::forget("gso_notifications_counts_{$user->user_id}");
$this->loadCounts();
$this->resetPage();

$this->success('Notification deleted.', position: 'toast-top');
$this->success('Notification deleted.', position: 'toast-top');
}
} catch (\Exception $e) {
Log::error('Failed to delete notification: ' . $e->getMessage());
$this->error('Failed to delete notification.', position: 'toast-top');
}
}

public function clearAllRead()
{
$user = auth()->user();
try {
$user = auth()->user();

// Only delete notifications that have been read
$deletedCount = $user->notifications()
->whereNotNull('read_at')
->delete();
// Only delete notifications that have been read
$deletedCount = $user->notifications()
->whereNotNull('read_at')
->delete();

// Clear cache to refresh counts
\Illuminate\Support\Facades\Cache::forget("osa_notifications_counts_{$user->user_id}");
$this->loadCounts();
$this->resetPage();
// Clear cache to refresh counts
Cache::forget("gso_notifications_counts_{$user->user_id}");
$this->loadCounts();
$this->resetPage();

if ($deletedCount > 0) {
$this->success("{$deletedCount} read notification(s) cleared.", position: 'toast-top');
} else {
$this->info('No read notifications to clear.', position: 'toast-top');
if ($deletedCount > 0) {
$this->success("{$deletedCount} read notification(s) cleared.", position: 'toast-top');
} else {
$this->info('No read notifications to clear.', position: 'toast-top');
}
} catch (\Exception $e) {
Log::error('Failed to clear read notifications: ' . $e->getMessage());
$this->error('Failed to clear read notifications.', position: 'toast-top');
}
}

Expand Down
Loading