What I want to achieve:
It integrates the UserObserver to listen for user status updates and broadcasts the UserStatusUpdated event. Additionally, it notifies the super admin about the user status update using Filament's Notification::make().
Pusher Log:

My Codes:
2014_10_12_000000_create_users_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('avatar_url', 2048)->nullable();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->string('status')->default('active');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};
User.php
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens;
use HasFactory;
use Notifiable;
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'avatar_url',
'name',
'email',
'password',
'status',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
UserStatusUpdated.php
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class UserStatusUpdated implements ShouldBroadcast
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public User $user,
) {
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('user.' . $this->user->id),
];
}
/**
* Get the data to broadcast.
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return [
'id' => $this->user->id,
'status' => $this->user->status,
];
}
}
UserObserver.php
<?php
namespace App\Observers;
use App\Enums\UserRoles;
use App\Events\UserStatusUpdated;
use App\Models\User;
use App\Notifications\UserDeleted;
use App\Notifications\UserForceDeleted;
use App\Notifications\Welcome;
use Filament\Notifications\Notification;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
use Spatie\Permission\Models\Role;
class UserObserver implements ShouldHandleEventsAfterCommit
{
/**
* Handle the User "created" event.
*/
public function created(User $user): void
{
//
}
/**
* Handle the User "updated" event.
*/
public function updated(User $user): void
{
// Check if the user's status has been updated.
if ($user->wasChanged('status')) {
// Broadcast event for user status update.
broadcast(new UserStatusUpdated($user));
// Notify 'super_admin' when a user status is updated.
$superAdminRole = Role::where('name', UserRoles::SuperAdmin)->first();
if ($superAdminRole) {
Notification::make()
->icon('bi-person')
->iconColor('primary')
->title(__('User Status Updated'))
->body($user->name . ' - ' . $user->email . __(' status updated to ') . $user->status)
->broadcast($superAdminRole->users);
}
}
}
/**
* Handle the User "deleted" event.
*/
public function deleted(User $user): void
{
//
}
/**
* Handle the User "restored" event.
*/
public function restored(User $user): void
{
//
}
/**
* Handle the User "force deleted" event.
*/
public function forceDeleted(User $user): void
{
//
}
}
channels.php
<?php
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;
/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Broadcast::routes();
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
}, ['middleware' => ['role:super_admin']]);
php artisan route:list
GET|POST|HEAD broadcasting/auth ........... Illuminate\Broadcasting › BroadcastController@authenticate
config/app.php
uncomment App\Providers\BroadcastServiceProvider provider in the providers array of your config/app.php configuration file.
config/filament.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Broadcasting
|--------------------------------------------------------------------------
|
| By uncommenting the Laravel Echo configuration, you may connect Filament
| to any Pusher-compatible websockets server.
|
| This will allow your users to receive real-time notifications.
|
*/
'broadcasting' => [
'echo' => [
'broadcaster' => 'pusher',
'key' => env('VITE_PUSHER_APP_KEY'),
'cluster' => env('VITE_PUSHER_APP_CLUSTER'),
'wsHost' => env('VITE_PUSHER_HOST'),
'wsPort' => env('VITE_PUSHER_PORT'),
'wssPort' => env('VITE_PUSHER_PORT'),
// 'authEndpoint' => '/api/v1/broadcasting/auth',
'disableStats' => true,
'encrypted' => true,
],
],
...
];
resources/js/bootstrap.js
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from "axios";
window.axios = axios;
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
import Echo from "laravel-echo";
import Pusher from "pusher-js";
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: "pusher",
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? "mt1",
wsHost: import.meta.env.VITE_PUSHER_HOST
? import.meta.env.VITE_PUSHER_HOST
: `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? "https") === "https",
enabledTransports: ["ws", "wss"],
});
var channel = Echo.private(`App.Models.User.${userId}`);
channel.listen("UserStatusUpdated", function (e) {
console.log(e.user.name);
});
.env
PUSHER_APP_ID=your-pusher-app-id
PUSHER_APP_KEY=your-pusher-key
PUSHER_APP_SECRET=your-pusher-secret
PUSHER_APP_CLUSTER=mt1
BROADCAST_DRIVER=pusher