feat(profile): show beacon monitors section on own user profile page
Add a Beacon monitors card to /u/:callsign that appears only when the viewer is the profile owner. Shows registered monitors with truncated token and last-seen timestamp, with a link to manage monitors in settings. Includes tests for visibility rules.
This commit is contained in:
parent
c193f35a0c
commit
739984d3bc
2 changed files with 109 additions and 1 deletions
|
|
@ -3,6 +3,7 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
|||
use MicrowavepropWeb, :live_view
|
||||
|
||||
alias Microwaveprop.Accounts
|
||||
alias Microwaveprop.BeaconMonitors
|
||||
alias Microwaveprop.Beacons
|
||||
alias Microwaveprop.Beacons.Beacon
|
||||
alias Microwaveprop.Format
|
||||
|
|
@ -23,13 +24,22 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
|||
beacons = Beacons.list_beacons_for_user(user, viewer)
|
||||
involving = Radio.list_contacts_involving_callsign(user.callsign, viewer)
|
||||
|
||||
monitors =
|
||||
if viewer && viewer.id == user.id do
|
||||
BeaconMonitors.list_monitors_for_user(user)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
page_title: user.callsign,
|
||||
profile: user,
|
||||
contacts: contacts,
|
||||
beacons: beacons,
|
||||
involving: involving
|
||||
involving: involving,
|
||||
beacon_monitors: monitors,
|
||||
is_own_profile: viewer && viewer.id == user.id
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
|
@ -155,6 +165,56 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div :if={@is_own_profile} class="card bg-base-200 shadow-sm mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Beacon monitors</h2>
|
||||
<p class="text-sm opacity-70">
|
||||
Remote monitor stations that report beacon reception data.
|
||||
</p>
|
||||
|
||||
<%= if @beacon_monitors == [] do %>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm opacity-70">No monitors registered yet.</p>
|
||||
<.link navigate={~p"/users/settings#beacon-monitors"} class="btn btn-primary btn-sm">
|
||||
<.icon name="hero-plus" class="size-4" /> Register a monitor
|
||||
</.link>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Token</th>
|
||||
<th>Last seen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :for={monitor <- @beacon_monitors}>
|
||||
<td class="font-semibold">{monitor.name}</td>
|
||||
<td>
|
||||
<code class="text-xs select-all">…{String.slice(monitor.token, -8, 8)}</code>
|
||||
</td>
|
||||
<td class="text-sm opacity-70">
|
||||
<%= if monitor.last_seen_at do %>
|
||||
{Calendar.strftime(monitor.last_seen_at, "%Y-%m-%d %H:%M UTC")}
|
||||
<% else %>
|
||||
never
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<.link navigate={~p"/users/settings#beacon-monitors"} class="btn btn-primary btn-sm">
|
||||
<.icon name="hero-plus" class="size-4" /> Register another monitor
|
||||
</.link>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Contacts {@profile.callsign} is in</h2>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule MicrowavepropWeb.UserProfileLiveTest do
|
|||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Microwaveprop.Accounts.User
|
||||
alias Microwaveprop.BeaconMonitors
|
||||
alias Microwaveprop.Beacons
|
||||
alias Microwaveprop.Radio.Contact
|
||||
alias Microwaveprop.Repo
|
||||
|
|
@ -217,5 +218,52 @@ defmodule MicrowavepropWeb.UserProfileLiveTest do
|
|||
|
||||
assert html =~ ~r/font-mono[^>]*>\s*W\s*</s
|
||||
end
|
||||
|
||||
test "anonymous visitors do not see beacon monitors section", %{conn: conn} do
|
||||
user = user_fixture(%{callsign: "W5ISP"})
|
||||
|
||||
{:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}")
|
||||
|
||||
refute html =~ "Beacon monitors"
|
||||
refute html =~ "No monitors registered yet"
|
||||
end
|
||||
|
||||
test "another logged-in user does not see beacon monitors section", %{conn: conn} do
|
||||
owner = user_fixture(%{callsign: "W5ISP"})
|
||||
other = user_fixture(%{callsign: "W5OTH"})
|
||||
|
||||
conn = log_in_user(conn, other)
|
||||
{:ok, _lv, html} = live(conn, ~p"/u/#{owner.callsign}")
|
||||
|
||||
refute html =~ "Beacon monitors"
|
||||
refute html =~ "No monitors registered yet"
|
||||
end
|
||||
|
||||
test "owners see empty-state when they have no beacon monitors", %{conn: conn} do
|
||||
user = user_fixture(%{callsign: "W5ISP"})
|
||||
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}")
|
||||
|
||||
assert html =~ "Beacon monitors"
|
||||
assert html =~ "No monitors registered yet"
|
||||
assert html =~ "Register a monitor"
|
||||
end
|
||||
|
||||
test "owners see their registered beacon monitors", %{conn: conn} do
|
||||
user = user_fixture(%{callsign: "W5ISP"})
|
||||
|
||||
{:ok, monitor} = BeaconMonitors.create_monitor(user, %{name: "Shack Pi"})
|
||||
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}")
|
||||
|
||||
assert html =~ "Beacon monitors"
|
||||
assert html =~ "Shack Pi"
|
||||
assert html =~ "never"
|
||||
assert html =~ "Register another monitor"
|
||||
# Token is shown truncated (last 8 chars)
|
||||
assert html =~ String.slice(monitor.token, -8, 8)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue