Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,546 @@
<?php
namespace App\Http\Livewire;
use App\Services\PresenceService;
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
use Cog\Laravel\Love\Reaction\Models\Reaction;
use Livewire\Attributes\On;
use Livewire\Component;
class OnlineReactedProfiles extends Component
{
// Configuration properties
public $reactionTypes = []; // ['Bookmark', 'Star', 'Like'] or single 'Bookmark'
public $guards = ['web']; // Which guards to check for online users
public $authGuard; // Current user's guard
public $showCount = true;
public $showAvatars = true;
public $maxDisplay = 20;
public $headerText; // Header text for the component
public $refreshInterval = 10;
public $groupByModel = true; // Group results by model type
public $lastSeen = false; // Show last seen time
public $modelLabels = [
'App\Models\User' => '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');
}
}