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
|
use MicrowavepropWeb, :live_view
|
||||||
|
|
||||||
alias Microwaveprop.Accounts
|
alias Microwaveprop.Accounts
|
||||||
|
alias Microwaveprop.BeaconMonitors
|
||||||
alias Microwaveprop.Beacons
|
alias Microwaveprop.Beacons
|
||||||
alias Microwaveprop.Beacons.Beacon
|
alias Microwaveprop.Beacons.Beacon
|
||||||
alias Microwaveprop.Format
|
alias Microwaveprop.Format
|
||||||
|
|
@ -23,13 +24,22 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
||||||
beacons = Beacons.list_beacons_for_user(user, viewer)
|
beacons = Beacons.list_beacons_for_user(user, viewer)
|
||||||
involving = Radio.list_contacts_involving_callsign(user.callsign, 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,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
page_title: user.callsign,
|
page_title: user.callsign,
|
||||||
profile: user,
|
profile: user,
|
||||||
contacts: contacts,
|
contacts: contacts,
|
||||||
beacons: beacons,
|
beacons: beacons,
|
||||||
involving: involving
|
involving: involving,
|
||||||
|
beacon_monitors: monitors,
|
||||||
|
is_own_profile: viewer && viewer.id == user.id
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -155,6 +165,56 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
||||||
</div>
|
</div>
|
||||||
</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 bg-base-200 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title">Contacts {@profile.callsign} is in</h2>
|
<h2 class="card-title">Contacts {@profile.callsign} is in</h2>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ defmodule MicrowavepropWeb.UserProfileLiveTest do
|
||||||
import Phoenix.LiveViewTest
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
alias Microwaveprop.Accounts.User
|
alias Microwaveprop.Accounts.User
|
||||||
|
alias Microwaveprop.BeaconMonitors
|
||||||
alias Microwaveprop.Beacons
|
alias Microwaveprop.Beacons
|
||||||
alias Microwaveprop.Radio.Contact
|
alias Microwaveprop.Radio.Contact
|
||||||
alias Microwaveprop.Repo
|
alias Microwaveprop.Repo
|
||||||
|
|
@ -217,5 +218,52 @@ defmodule MicrowavepropWeb.UserProfileLiveTest do
|
||||||
|
|
||||||
assert html =~ ~r/font-mono[^>]*>\s*W\s*</s
|
assert html =~ ~r/font-mono[^>]*>\s*W\s*</s
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue