defmodule PhoenixKitWeb.Live.NotificationsBell do @moduledoc """ Nested LiveView that renders the notifications bell in the global layout. Embedded from `layout_wrapper.ex` via `live_render/3` with `sticky: true` so it keeps its socket across page navigations and its own PubSub subscription stays live. It owns its `handle_info` callbacks for `{:notification_created, _}` / `{:notification_seen, _}` / `{:notification_dismissed, _}` / `{:notifications_bulk_updated, _}`, refreshing the badge count and dropdown contents on each event. Not routable on its own — mount expects `user_uuid` in the nested session. """ use Phoenix.LiveView, layout: false use Gettext, backend: PhoenixKitWeb.Gettext require Logger import PhoenixKitWeb.Components.Core.Icon, only: [icon: 1] alias PhoenixKit.Notifications alias PhoenixKit.Notifications.Events alias PhoenixKit.Notifications.Render alias PhoenixKit.Settings alias PhoenixKit.Utils.Routes def mount(_params, %{"user_uuid" => user_uuid} = session, socket) when is_binary(user_uuid) do if connected?(socket), do: Events.subscribe(user_uuid) # Recipient's current locale, threaded from the layout so click-through links # land on the right locale-prefixed path (the bell is a sticky nested LV with # no locale of its own — without this, Routes.path would use the default). {:ok, socket |> assign(:user_uuid, user_uuid) |> assign(:locale, session["locale"]) |> refresh()} end # Graceful fallback for embeddings without a user session — renders an # empty element so the layout doesn't crash for anonymous visitors. def mount(_params, _session, socket) do {:ok, socket |> assign(:user_uuid, nil) |> assign(:locale, nil)} end # ── Events ───────────────────────────────────────────────────────── def handle_event("open_notification", %{"uuid" => uuid}, socket) do user_uuid = socket.assigns.user_uuid case Notifications.mark_seen(user_uuid, uuid) do {:ok, notification} -> # Effective target: the notification's own link, else the host's # configured catch-all default. Both are nil → no navigation (the row # is informational; the click still cleared its unread state above). link = Render.render(notification, socket.assigns.locale).link target = link || socket.assigns.default_link case target do nil -> warn_unlinked(notification) {:noreply, refresh(socket)} target -> {:noreply, push_navigate(socket, to: target)} end _ -> {:noreply, refresh(socket)} end end def handle_event("dismiss", %{"uuid" => uuid}, socket) do Notifications.dismiss(socket.assigns.user_uuid, uuid) {:noreply, refresh(socket)} end def handle_event("mark_all_seen", _params, socket) do Notifications.mark_all_seen(socket.assigns.user_uuid) {:noreply, refresh(socket)} end # ── PubSub ───────────────────────────────────────────────────────── def handle_info({event, _payload}, socket) when event in [ :notification_created, :notification_seen, :notification_dismissed, :notifications_bulk_updated ] do {:noreply, refresh(socket)} end def handle_info(_msg, socket), do: {:noreply, socket} # ── Render ───────────────────────────────────────────────────────── def render(%{user_uuid: nil} = assigns) do ~H"""
""" end def render(assigns) do ~H"""