feat/reverb (#20)

Co-authored-by: jon brookes <marshyon@gmail.com>
Reviewed-on: https://codeberg.org/headshed/share-lt/pulls/20
This commit is contained in:
Jon Brookes 2026-02-14 17:49:01 +01:00
parent 74bc17d019
commit 21147af908
30 changed files with 1948 additions and 29 deletions

View file

@ -0,0 +1,38 @@
<?php
namespace App\Console\Commands;
use App\Events\PreviewSiteBuilt;
use Illuminate\Console\Command;
class SendPreviewSiteBuiltNotification extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test:preview-site-built {--message=Preview site is built}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a test notification that the preview site is built';
/**
* Execute the console command.
*/
public function handle(): void
{
$message = $this->option('message');
$this->info("Command :: Broadcasting preview site built notification: {$message}");
PreviewSiteBuilt::dispatch($message, 'success');
$this->info('Notification broadcasted successfully!');
$this->info('Check your Filament admin panel for the toast notification.');
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PreviewSiteBuilt implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public string $message = 'Preview site is built',
public string $type = 'success'
) {}
public function broadcastOn(): array
{
return [
new Channel('filament-notifications'),
];
}
public function broadcastAs(): string
{
return 'preview-site.built';
}
}

30
app/Events/TestEvent.php Normal file
View file

@ -0,0 +1,30 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TestEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public string $message = 'Test message from Laravel'
) {}
public function broadcastOn(): array
{
return [
new Channel('test-channel'),
];
}
public function broadcastAs(): string
{
return 'test.message';
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers;
use App\Events\PreviewSiteBuilt;
use App\Http\Requests\SendPreviewSiteBuiltNotificationRequest;
use Illuminate\Http\JsonResponse;
class NotificationController extends Controller
{
/**
* Send preview site built notification.
*/
public function sendPreviewSiteBuilt(SendPreviewSiteBuiltNotificationRequest $request): JsonResponse
{
$message = $request->validated()['message'];
PreviewSiteBuilt::dispatch($message, 'success');
return response()->json([
'success' => true,
'message' => 'Preview site built notification sent successfully',
'data' => [
'notification_message' => $message,
],
]);
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SendPreviewSiteBuiltNotificationRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->user() && $this->user()->email === config('app.admin_email');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'message' => 'required|string|max:255',
];
}
}

View file

@ -71,5 +71,155 @@ class AdminPanelProvider extends PanelProvider
PanelsRenderHook::BODY_END,
fn(): string => \Illuminate\Support\Facades\Blade::render('@vite("resources/js/app.js")'),
);
FilamentView::registerRenderHook(
PanelsRenderHook::BODY_END,
function (): string {
return '
<script>
document.addEventListener("DOMContentLoaded", function() {
if (window.Echo) {
<<<<<<< HEAD
=======
>>>>>>> 4143902e603c7e3e25de5e5f49c1116dd5a5743b
console.log("Setting up Filament Reverb notifications...");
console.log("Available globals:", {
FilamentNotification: typeof FilamentNotification,
Livewire: typeof window.Livewire,
filament: typeof window.filament
});
<<<<<<< HEAD
=======
>>>>>>> 4143902e603c7e3e25de5e5f49c1116dd5a5743b
// Listen for preview site built events
window.Echo.channel("filament-notifications")
.listen("preview-site.built", function(event) {
console.log("🎉 Received preview site built event:", event);
// Use Filament v4 notification system
if (typeof FilamentNotification !== "undefined") {
new FilamentNotification()
.title(event.message)
.success()
.duration(5000)
.send();
console.log("✅ Sent via FilamentNotification v4");
} else {
console.warn("FilamentNotification not available");
// Fallback notification
const notification = document.createElement("div");
notification.style.cssText = `
position: fixed !important;
top: 20px !important;
right: 20px !important;
z-index: 999999 !important;
background: #10b981 !important;
color: white !important;
padding: 16px 20px !important;
border-radius: 8px !important;
box-shadow: 0 10px 25px rgba(0,0,0,0.3) !important;
font-family: system-ui, -apple-system, sans-serif !important;
font-size: 14px !important;
font-weight: 500 !important;
max-width: 350px !important;
display: block !important;
opacity: 1 !important;
`;
notification.innerHTML = `
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="margin-right: 15px;">
${event.message}
</div>
<button onclick="this.parentElement.parentElement.remove()"
style="background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0;">
×
</button>
</div>
`;
document.body.appendChild(notification);
console.log("✅ Created fallback notification");
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
})
.listen(".preview-site.built", function(event) {
console.log("🔄 Also listening with dot prefix (the working one):", event);
<<<<<<< HEAD
console.log("message:", event.message);
=======
>>>>>>> 4143902e603c7e3e25de5e5f49c1116dd5a5743b
// Use Filament v4 notification system
if (typeof FilamentNotification !== "undefined") {
new FilamentNotification()
.title(event.message)
.success()
.duration(5000)
.send();
console.log("✅ Sent via FilamentNotification v4 (from dot prefix)");
} else {
console.warn("FilamentNotification not available, creating fallback");
// Fallback notification
const notification = document.createElement("div");
notification.style.cssText = `
position: fixed !important;
top: 20px !important;
right: 20px !important;
z-index: 999999 !important;
background: #3b82f6 !important;
color: white !important;
padding: 16px 20px !important;
border-radius: 8px !important;
box-shadow: 0 10px 25px rgba(0,0,0,0.3) !important;
font-family: system-ui, -apple-system, sans-serif !important;
font-size: 14px !important;
font-weight: 500 !important;
max-width: 350px !important;
display: block !important;
opacity: 1 !important;
`;
notification.innerHTML = `
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="margin-right: 15px;">
🔄 ${event.message}
</div>
<button onclick="this.parentElement.parentElement.remove()"
style="background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0;">
×
</button>
</div>
`;
document.body.appendChild(notification);
console.log("✅ Created dot prefix fallback notification");
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
});
console.log("✅ Event listeners set up for filament-notifications channel");
} else {
console.error("❌ Echo is not available for Filament notifications");
}
});
</script>';
}
);
}
}