dispatch('openModal', component: 'modals.create-user'); } /** * Destructive (R5): open the confirm modal for removing an account. Guards * (self / last account) are enforced here AND again in the apply handler, so * a stale event can never slip past after the modal is open. */ public function confirmRemove(int $userId): void { $user = User::find($userId); if (! $user || ! $this->canRemove($user)) { return; } $this->dispatch('openModal', component: 'modals.confirm-action', arguments: [ 'heading' => __('accounts.remove_heading'), 'body' => __('accounts.remove_body'), 'confirmLabel' => __('accounts.remove'), 'danger' => true, 'icon' => 'trash', 'auditTarget' => $user->email, 'event' => 'userRemoved', 'params' => ['userId' => $userId], // Defer the toast: the apply handler reports the real outcome. 'notify' => '', ], ); } #[On('userRemoved')] public function remove(int $userId, SessionService $sessions): void { $user = User::find($userId); if (! $user || ! $this->canRemove($user)) { return; } $email = $user->email; // Kill every session + rotate the remember_token BEFORE deleting, so a // stolen remember-me cookie can never resurrect the account between the // session wipe and the row delete. $sessions->logoutUserEverywhere($user); $user->delete(); $this->audit('user.delete', $email); $this->dispatch('notify', message: __('accounts.remove_notify')); } /** * Destructive (R5): open the confirm modal for signing another account out * of every device. Never offered for self (self uses Settings → Sessions). */ public function confirmLogout(int $userId): void { $user = User::find($userId); if (! $user || $user->is(Auth::user())) { return; } $this->dispatch('openModal', component: 'modals.confirm-action', arguments: [ 'heading' => __('accounts.logout_heading'), 'body' => __('accounts.logout_body'), 'confirmLabel' => __('accounts.logout'), 'danger' => true, 'icon' => 'logout', 'auditTarget' => $user->email, 'event' => 'userLoggedOut', 'params' => ['userId' => $userId], 'notify' => '', ], ); } #[On('userLoggedOut')] public function logout(int $userId, SessionService $sessions): void { $user = User::find($userId); if (! $user || $user->is(Auth::user())) { return; } $sessions->logoutUserEverywhere($user); $this->audit('user.logout', $user->email); $this->dispatch('notify', message: __('accounts.logout_notify')); } /** Reload the list after the CreateUser modal adds an account. */ #[On('usersChanged')] public function refreshList(): void { // The list is re-fetched in render(); this handler just forces the round trip. } /** * A user may be removed unless it is the current operator or the last * remaining account (no lock-out). Mirrors the create-user "equal admins" * model — anyone can remove anyone else. */ private function canRemove(User $user): bool { return ! $user->is(Auth::user()) && User::count() > 1; } private function audit(string $action, ?string $target): void { AuditEvent::create([ 'user_id' => Auth::id(), 'actor' => Auth::user()?->name ?? 'system', 'action' => $action, 'target' => $target, 'ip' => request()->ip(), ]); } public function render() { return view('livewire.settings.users', [ 'users' => User::query()->orderBy('name')->get(), 'currentId' => Auth::id(), ]); } }