Use notifications badge

This commit is contained in:
Eugene Burmakin 2024-11-03 15:21:29 +01:00
parent 14b7397840
commit bec9db1198
5 changed files with 61 additions and 33 deletions

File diff suppressed because one or more lines are too long

View file

@ -2,46 +2,73 @@ import { Controller } from "@hotwired/stimulus"
import consumer from "../channels/consumer" import consumer from "../channels/consumer"
export default class extends Controller { export default class extends Controller {
static targets = ["container"] static targets = ["badge"]
static values = { userId: Number } static values = { userId: Number }
initialize() {
this.subscription = null
}
connect() { connect() {
console.log("Controller connecting...") console.log("[Stimulus] Notifications controller connecting...")
// Ensure we clean up any existing subscription
// Clean up any existing subscription
if (this.subscription) { if (this.subscription) {
console.log("Cleaning up existing subscription") console.log("[Stimulus] Cleaning up existing subscription")
this.subscription.unsubscribe() this.subscription.unsubscribe()
this.subscription = null
} }
this.subscription = consumer.subscriptions.create("NotificationsChannel", { // Create new subscription
connected: () => { this.createSubscription()
console.log("Connected to NotificationsChannel", this.subscription)
},
disconnected: () => {
console.log("Disconnected from NotificationsChannel")
},
received: (data) => {
console.log("Received notification:", data, "Subscription:", this.subscription)
this.displayNotification(data)
}
})
} }
disconnect() { disconnect() {
console.log("Controller disconnecting...") console.log("[Stimulus] Notifications controller disconnecting...")
if (this.subscription) { if (this.subscription) {
this.subscription.unsubscribe() this.subscription.unsubscribe()
this.subscription = null this.subscription = null
} }
} }
displayNotification(data) { createSubscription() {
console.log("Notification received:", data) // For debugging console.log("[Stimulus] Creating new notification subscription")
const notification = document.createElement("div") this.subscription = consumer.subscriptions.create("NotificationsChannel", {
notification.classList.add("notification", `notification-${data.kind}`) connected: () => {
notification.innerHTML = `<strong>${data.title}</strong>: ${data.content}` console.log("[WebSocket] Connected to NotificationsChannel")
},
disconnected: () => {
console.log("[WebSocket] Disconnected from NotificationsChannel")
},
received: (data) => {
console.log("[WebSocket] Received notification:", data)
this.animateBadge()
}
})
}
this.containerTarget.appendChild(notification) animateBadge() {
setTimeout(() => notification.remove(), 5000) // Auto-hide after 5 seconds let badge = this.hasBadgeTarget ? this.badgeTarget : null
if (!badge) {
badge = document.createElement("span")
badge.className = "badge badge-xs badge-primary absolute top-0 right-0"
badge.setAttribute("data-notifications-target", "badge")
this.element.querySelector('.btn').appendChild(badge)
}
// Create ping effect div if it doesn't exist
let pingEffect = badge.querySelector('.ping-effect')
if (!pingEffect) {
pingEffect = document.createElement("span")
pingEffect.className = "ping-effect absolute inline-flex h-full w-full rounded-full animate-ping bg-primary opacity-75"
badge.appendChild(pingEffect)
} else {
// Reset animation
pingEffect.remove()
requestAnimationFrame(() => {
badge.appendChild(pingEffect)
})
}
} }
} }

View file

@ -18,13 +18,14 @@ class Notification < ApplicationRecord
private private
def broadcast_notification def broadcast_notification
Rails.logger.debug "Broadcasting notification to #{user.id}"
NotificationsChannel.broadcast_to( NotificationsChannel.broadcast_to(
user, user,
{ {
id: id,
title: title, title: title,
content: content, content: content,
kind: kind kind: kind,
timestamp: Time.current.to_i
} }
) )
end end

View file

@ -21,9 +21,6 @@
<div class='container mx-auto'> <div class='container mx-auto'>
<%= render 'shared/navbar' %> <%= render 'shared/navbar' %>
<%= render 'shared/flash' %> <%= render 'shared/flash' %>
<div data-controller="notifications" data-notifications-user-id-value="<%= current_user.id %>">
<div data-notifications-target="container" class="notifications-container"></div>
</div>
<div class="flex flex-row gap-5 w-full px-5"> <div class="flex flex-row gap-5 w-full px-5">
<%= yield %> <%= yield %>
</div> </div>

View file

@ -53,7 +53,9 @@
<div class="navbar-end"> <div class="navbar-end">
<ul class="menu menu-horizontal bg-base-100 rounded-box px-1"> <ul class="menu menu-horizontal bg-base-100 rounded-box px-1">
<% if user_signed_in? %> <% if user_signed_in? %>
<div class="dropdown dropdown-end dropdown-bottom"> <div class="dropdown dropdown-end dropdown-bottom"
data-controller="notifications"
data-notifications-user-id-value="<%= current_user.id %>">
<div tabindex="0" role="button" class='btn btn-sm btn-ghost hover:btn-ghost'> <div tabindex="0" role="button" class='btn btn-sm btn-ghost hover:btn-ghost'>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -68,7 +70,8 @@
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /> d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg> </svg>
<% if @unread_notifications.present? %> <% if @unread_notifications.present? %>
<span class="badge badge-xs badge-primary"></span> <span class="badge badge-xs badge-primary absolute top-0 right-0"
data-notifications-target="badge"></span>
<% end %> <% end %>
</div> </div>
<ul tabindex="0" class="dropdown-content z-10 menu p-2 shadow-lg bg-base-100 rounded-box min-w-52"> <ul tabindex="0" class="dropdown-content z-10 menu p-2 shadow-lg bg-base-100 rounded-box min-w-52">