'Persons', 'App\Models\Organization' => 'Organizations', 'App\Models\Bank' => 'Banks', 'App\Models\Admin' => 'Admins', ]; // Data properties public $onlineReactedProfiles = []; public $totalCount = 0; public $countByType = []; // Logout modal properties public $showLogoutModal = false; public $selectedProfileId = null; public $selectedProfileType = null; public $selectedProfileName = null; public function mount( $reactionTypes = null, $guards = null, $showCount = true, $showAvatars = true, $maxDisplay = 20, $header = true, $headerText = null, $groupByModel = false, $lastSeen = false ) { // Setup reaction types if ($reactionTypes !== null) { if (is_string($reactionTypes)) { $this->reactionTypes = array_map('trim', explode(',', $reactionTypes)); } else { $this->reactionTypes = is_array($reactionTypes) ? $reactionTypes : [$reactionTypes]; } } else { // Keep as null to show all online users without filtering $this->reactionTypes = null; } // Setup guards to check if ($guards) { $this->guards = is_array($guards) ? $guards : [$guards]; } else { // Check all guards by default $this->guards = ['web', 'organization', 'bank']; } // Determine current auth guard $this->authGuard = session('active_guard') ?? 'web'; // Set display options $this->showCount = $showCount; $this->showAvatars = $showAvatars; $this->maxDisplay = $maxDisplay; $this->groupByModel = $groupByModel; $this->lastSeen = $lastSeen; // Load initial data $this->loadOnlineReactedProfiles(); // Set header text if required if ($header) { if ($headerText) { $this->headerText = $headerText; } else { $this->headerText = $this->getHeaderText($this->reactionTypes); } } else { $this->headerText = null; } } /** * Get the header text based on reaction types. * If no reaction types, return a default message. */ public function getHeaderText($reactionTypes) { if ($this->reactionTypes === null) { // Show all online users return trans_choice('messages.profiles_online', $this->totalCount, ['count' => $this->totalCount]); } elseif (!empty($reactionTypes)) { if (count($reactionTypes) === 1) { return trans_choice('messages.' . strtolower($reactionTypes[0]) . '_contacts_online', $this->totalCount, ['count' => $this->totalCount]); } elseif (count($reactionTypes) > 1) { return trans_choice('messages.reactions_contacts_online', $this->totalCount, ['count' => $this->totalCount]); } } return 'Error: no reaction types found.'; } public function loadOnlineReactedProfiles() { try { // Get authenticated user/profile $authUser = auth($this->authGuard)->user(); if (!$authUser) { $this->onlineReactedProfiles = []; $this->totalCount = 0; return; } // Only require viaLoveReacter if filtering by reactions if ($this->reactionTypes !== null && !method_exists($authUser, 'viaLoveReacter')) { $this->onlineReactedProfiles = []; $this->totalCount = 0; return; } // Collect all online users from specified guards $allOnlineProfiles = collect(); foreach ($this->guards as $guard) { try { $presenceService = app(PresenceService::class); $onlineUsers = $presenceService->getOnlineUsers($guard); foreach ($onlineUsers as $userData) { // Get the actual model instance $model = $this->getModelFromUserData($userData, $guard); // If filtering by reactions, check for love reactant trait // If not filtering (reactionTypes is null), just check if model exists if ($model && ($this->reactionTypes === null || method_exists($model, 'viaLoveReactant'))) { $profileData = $this->buildProfileData($model, $userData, $guard); if ($profileData) { $allOnlineProfiles->push($profileData); } } } } catch (\Exception $e) { \Log::error('Error loading online profiles for guard ' . $guard . ': ' . $e->getMessage()); } } // Filter profiles based on whether we're filtering by reactions or not if ($this->reactionTypes === null) { // Show all online profiles without reaction filtering $reactedProfiles = $allOnlineProfiles; } else { // Filter profiles that have been reacted to $reactedProfiles = $allOnlineProfiles->filter(function ($profile) { return $this->hasAuthUserReacted($profile['model']); }); } // Apply display limit $limitedProfiles = $reactedProfiles->take($this->maxDisplay); // Group by model if requested if ($this->groupByModel) { $this->onlineReactedProfiles = $limitedProfiles ->groupBy('model_type') ->toArray(); } else { $this->onlineReactedProfiles = $limitedProfiles->values()->toArray(); } // Update counts $this->totalCount = $reactedProfiles->count(); $this->countByType = $reactedProfiles->groupBy('model_type')->map->count()->toArray(); // Dispatch event for other components $this->dispatch('online-reacted-profiles-updated', [ 'count' => $this->totalCount, 'profiles' => $this->onlineReactedProfiles ]); } catch (\Exception $e) { \Log::error('Error loading online reacted profiles: ' . $e->getMessage()); $this->onlineReactedProfiles = []; $this->totalCount = 0; } } protected function getModelFromUserData($userData, $guard) { try { // Get the model class for this guard $modelClass = $this->getModelClassForGuard($guard); if (!$modelClass) { return null; } // Handle both array and object formats $userId = is_array($userData) ? ($userData['id'] ?? null) : ($userData->id ?? null); if (!$userId) { return null; } return $modelClass::find($userId); } catch (\Exception $e) { \Log::error('Error getting model from user data: ' . $e->getMessage()); return null; } } protected function getModelClassForGuard($guard) { // Map guards to model classes $guardModelMap = [ 'web' => \App\Models\User::class, 'organization' => \App\Models\Organization::class, 'bank' => \App\Models\Bank::class, 'admin' => \App\Models\Admin::class, ]; return $guardModelMap[$guard] ?? null; } protected function buildProfileData($model, $userData, $guard) { try { // Get reactions for this profile $reactions = $this->getReactionsForProfile($model); // Build profile data array return [ 'id' => $model->id, 'model' => $model, 'model_type' => get_class($model), 'model_label' => $this->modelLabels[get_class($model)] ?? class_basename($model), 'guard' => $guard, 'name' => $model->name ?? 'Unknown', 'location' => $model->getLocationFirst()['name_short'] ?? '', 'avatar' => $model->profile_photo_path ?? null, 'last_seen' => is_array($userData) ? ($userData['last_seen'] ?? now()) : ($userData->last_seen ?? now()), 'reactions' => $reactions, 'profile_url' => route('profile.show_by_type_and_id', ['type' => __(strtolower(class_basename($model))), 'id' => $model->id]), 'is_online' => true, ]; } catch (\Exception $e) { \Log::error('Error building profile data: ' . $e->getMessage()); return null; } } protected function hasAuthUserReacted($model) { try { // If no reaction types specified, return true (show all) if ($this->reactionTypes === null) { return true; } if (!method_exists($model, 'viaLoveReactant')) { return false; } $reactantFacade = $model->viaLoveReactant(); // Get the authenticated user directly $authUser = auth($this->authGuard)->user(); if (!$authUser) { return false; } // Check if empty array (different from null) if (is_array($this->reactionTypes) && empty($this->reactionTypes)) { return false; } foreach ($this->reactionTypes as $reactionType) { if ($reactantFacade->isReactedBy($authUser, $reactionType)) { return true; } } return false; } catch (\Exception $e) { \Log::error('Error checking reactions: ' . $e->getMessage()); return false; } } protected function getReactionsForProfile($model) { try { if (!method_exists($model, 'viaLoveReactant')) { return []; } $reactantFacade = $model->viaLoveReactant(); $reactions = []; // Get the authenticated user directly $authUser = auth($this->authGuard)->user(); if (!$authUser) { return []; } if ($this->reactionTypes !== null) { foreach ($this->reactionTypes as $reactionType) { if ($reactantFacade->isReactedBy($authUser, $reactionType)) { $reactions[] = [ 'type' => $reactionType, ]; } } } return $reactions; } catch (\Exception $e) { \Log::error('Error getting reactions for profile: ' . $e->getMessage()); return []; } } public function toggleReaction($profileId, $modelType, $reactionType) { try { $authUser = auth($this->authGuard)->user(); if (!$authUser) { return; } $model = $modelType::find($profileId); if (!$model) { return; } $reacterFacade = $authUser->viaLoveReacter(); $reactantFacade = $model->viaLoveReactant(); if ($reactantFacade->isReactedBy($authUser, $reactionType)) { $reacterFacade->unreactTo($model, $reactionType); } else { $reacterFacade->reactTo($model, $reactionType); } $this->loadOnlineReactedProfiles(); } catch (\Exception $e) { \Log::error('Error toggling reaction: ' . $e->getMessage()); } } #[On('presence-updated')] public function refreshProfiles() { $this->loadOnlineReactedProfiles(); } #[On('user-activity')] public function onUserActivity() { $this->loadOnlineReactedProfiles(); } public function isCurrentUserProfile($profileId, $modelType) { // Get the active profile using getActiveProfile() helper $activeProfile = getActiveProfile(); if (!$activeProfile) { return false; } // Check if this is the current active profile (admin or any other type) if (get_class($activeProfile) === $modelType || get_class($activeProfile) === '\\' . $modelType) { if ($activeProfile->id === $profileId) { return true; } } // If the active profile is an Admin, check for related users if (get_class($activeProfile) === \App\Models\Admin::class) { if (method_exists($activeProfile, 'users')) { $relatedUsers = $activeProfile->users()->pluck('users.id')->toArray(); if ($modelType === 'App\Models\User' || $modelType === \App\Models\User::class) { return in_array($profileId, $relatedUsers); } } } return false; } public function openLogoutModal($profileId, $modelType) { // Verify admin access if (getActiveProfileType() !== 'Admin') { $this->dispatch('notify', [ 'type' => 'error', 'message' => 'Unauthorized action. Only administrators can log out users.' ]); return; } // Prevent logging out current admin or their related profiles if ($this->isCurrentUserProfile($profileId, $modelType)) { $this->dispatch('notify', [ 'type' => 'error', 'message' => 'You cannot log out your own profile or related user accounts.' ]); return; } $this->selectedProfileId = $profileId; $this->selectedProfileType = $modelType; // Get profile name for display try { $model = $modelType::find($profileId); $this->selectedProfileName = $model ? $model->name : null; } catch (\Exception $e) { $this->selectedProfileName = null; } $this->showLogoutModal = true; } public function closeLogoutModal() { $this->showLogoutModal = false; $this->selectedProfileId = null; $this->selectedProfileType = null; $this->selectedProfileName = null; } public function logoutUser() { // Verify admin access if (getActiveProfileType() !== 'Admin') { $this->dispatch('notify', [ 'type' => 'error', 'message' => 'Unauthorized action. Only administrators can log out users.' ]); $this->closeLogoutModal(); return; } try { if (!$this->selectedProfileId || !$this->selectedProfileType) { throw new \Exception('Invalid profile data'); } $model = $this->selectedProfileType::find($this->selectedProfileId); if (!$model) { throw new \Exception('User not found'); } // Get the guard for this model type $guard = $this->getGuardForModelType($this->selectedProfileType); if (!$guard) { throw new \Exception('Invalid guard'); } // Broadcast logout event via websocket - this will force the user's browser to logout broadcast(new \App\Events\UserForcedLogout($this->selectedProfileId, $guard)); // Delete all sessions for this user from the database \DB::connection(config('session.connection')) ->table(config('session.table', 'sessions')) ->where('user_id', $this->selectedProfileId) ->delete(); // Clear any cached authentication data \Cache::forget('auth_' . $guard . '_' . $this->selectedProfileId); // Clear presence cache \Cache::forget("presence_{$guard}_{$this->selectedProfileId}"); // Clear online users cache to force refresh \Cache::forget("online_users_{$guard}_" . \App\Services\PresenceService::ONLINE_THRESHOLD_MINUTES); // Set user offline in presence service $presenceService = app(\App\Services\PresenceService::class); $presenceService->setUserOffline($model, $guard); $this->dispatch('notify', [ 'type' => 'success', 'message' => __('User has been logged out successfully.') ]); // Refresh the online profiles list $this->loadOnlineReactedProfiles(); } catch (\Exception $e) { \Log::error('Error logging out user: ' . $e->getMessage()); $this->dispatch('notify', [ 'type' => 'error', 'message' => __('Failed to log out user. Please try again.') ]); } $this->closeLogoutModal(); } protected function getGuardForModelType($modelType) { // Map model types to guards $modelGuardMap = [ \App\Models\User::class => 'web', 'App\Models\User' => 'web', \App\Models\Organization::class => 'organization', 'App\Models\Organization' => 'organization', \App\Models\Bank::class => 'bank', 'App\Models\Bank' => 'bank', \App\Models\Admin::class => 'admin', 'App\Models\Admin' => 'admin', ]; return $modelGuardMap[$modelType] ?? null; } public function render() { return view('livewire.online-reacted-profiles'); } }