Skip to content

Commit 28db665

Browse files
authored
Merge pull request #5 from chrisgreg/fix-nested-route-support-for-inboxlive
Fix nested route support for InboxLive
2 parents 5acd4ca + 15fc65e commit 28db665

File tree

3 files changed

+124
-9
lines changed

3 files changed

+124
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- **Nested route support** - InboxLive now correctly handles URLs when mounted in nested scopes (e.g., `/admin/fyi` instead of just `/fyi`)
13+
- Event detail URLs now respect the route prefix where the LiveView is mounted
14+
- Navigation between index and detail views works correctly regardless of scope nesting
15+
1016
## [1.0.1] - 2025-12-27
1117

1218
### Added

lib/fyi/web/inbox_live.ex

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ if Code.ensure_loaded?(Phoenix.LiveView) do
3535
end
3636

3737
@impl true
38-
def handle_params(params, _uri, socket) do
38+
def handle_params(params, uri, socket) do
3939
time_range = params["range"] || "7d"
4040
event_type = params["type"] || ""
4141
event_id = params["id"]
4242

43+
# Extract route prefix from URI
44+
route_prefix = extract_route_prefix_from_uri(uri)
45+
4346
socket =
4447
socket
48+
|> assign(:route_prefix, route_prefix)
4549
|> assign(:time_range, time_range)
4650
|> assign(:event_type, event_type)
4751
|> load_event_types()
@@ -71,24 +75,39 @@ if Code.ensure_loaded?(Phoenix.LiveView) do
7175

7276
@impl true
7377
def handle_event("time_range", %{"range" => range}, socket) do
74-
{:noreply, push_patch(socket, to: build_url(range, socket.assigns.event_type))}
78+
{:noreply, push_patch(socket, to: build_url(socket, range, socket.assigns.event_type))}
7579
end
7680

7781
@impl true
7882
def handle_event("event_type", %{"type" => type}, socket) do
79-
{:noreply, push_patch(socket, to: build_url(socket.assigns.time_range, type))}
83+
{:noreply, push_patch(socket, to: build_url(socket, socket.assigns.time_range, type))}
8084
end
8185

8286
@impl true
8387
def handle_event("close_detail", _, socket) do
8488
{:noreply,
85-
push_patch(socket, to: build_url(socket.assigns.time_range, socket.assigns.event_type))}
89+
push_patch(socket,
90+
to: build_url(socket, socket.assigns.time_range, socket.assigns.event_type)
91+
)}
92+
end
93+
94+
@doc false
95+
def extract_route_prefix_from_uri(uri) do
96+
# Extract the route prefix from the URI
97+
# For example: "http://localhost:4000/admin/fyi?range=7d" -> "/admin/fyi"
98+
# "http://localhost:4000/fyi/events/123?range=7d" -> "/fyi"
99+
uri
100+
|> URI.parse()
101+
|> Map.get(:path, "/fyi")
102+
|> String.split("/events/")
103+
|> List.first()
86104
end
87105

88-
defp build_url(range, type) do
106+
@doc false
107+
def build_url(socket, range, type) do
89108
params = [{"range", range}]
90109
params = if type != "", do: params ++ [{"type", type}], else: params
91-
"/fyi?" <> URI.encode_query(params)
110+
"#{socket.assigns.route_prefix}?" <> URI.encode_query(params)
92111
end
93112

94113
@impl true
@@ -697,7 +716,7 @@ if Code.ensure_loaded?(Phoenix.LiveView) do
697716
</div>
698717
<% else %>
699718
<%= for event <- @events do %>
700-
<.link patch={event_url(event.id, @time_range, @event_type)} class="fyi-event-row">
719+
<.link patch={event_url(assigns, event.id, @time_range, @event_type)} class="fyi-event-row">
701720
<svg class="fyi-event-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
702721
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
703722
</svg>
@@ -849,10 +868,18 @@ if Code.ensure_loaded?(Phoenix.LiveView) do
849868
end
850869
end
851870

852-
defp event_url(event_id, range, type) do
871+
@doc false
872+
def event_url(socket_or_assigns, event_id, range, type) do
873+
route_prefix =
874+
case socket_or_assigns do
875+
%{assigns: %{route_prefix: prefix}} -> prefix
876+
%{route_prefix: prefix} -> prefix
877+
_ -> "/fyi"
878+
end
879+
853880
params = [{"range", range}]
854881
params = if type != "", do: params ++ [{"type", type}], else: params
855-
"/fyi/events/#{event_id}?" <> URI.encode_query(params)
882+
"#{route_prefix}/events/#{event_id}?" <> URI.encode_query(params)
856883
end
857884

858885
defp time_range_since(range) do

test/fyi/web/inbox_live_test.exs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
defmodule FYI.Web.InboxLiveTest do
2+
use ExUnit.Case, async: true
3+
4+
alias FYI.Web.InboxLive
5+
6+
describe "extract_route_prefix_from_uri/1" do
7+
test "extracts route prefix from root-level /fyi path" do
8+
uri = "http://localhost:4000/fyi?range=7d"
9+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/fyi"
10+
end
11+
12+
test "extracts route prefix from nested /admin/fyi path" do
13+
uri = "http://localhost:4000/admin/fyi?range=7d"
14+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi"
15+
end
16+
17+
test "extracts route prefix from deeply nested path" do
18+
uri = "http://localhost:4000/admin/dashboard/fyi?range=7d"
19+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/dashboard/fyi"
20+
end
21+
22+
test "extracts route prefix from event detail URL at root level" do
23+
uri = "http://localhost:4000/fyi/events/123?range=7d"
24+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/fyi"
25+
end
26+
27+
test "extracts route prefix from event detail URL in nested scope" do
28+
uri = "http://localhost:4000/admin/fyi/events/456?range=7d&type=user"
29+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi"
30+
end
31+
32+
test "handles URI without query params" do
33+
uri = "http://localhost:4000/admin/fyi"
34+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi"
35+
end
36+
37+
test "handles URI with trailing slash" do
38+
uri = "http://localhost:4000/admin/fyi/"
39+
assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi/"
40+
end
41+
end
42+
43+
describe "event_url/4" do
44+
test "generates correct URL for root-level scope" do
45+
socket = %{assigns: %{route_prefix: "/fyi"}}
46+
url = InboxLive.event_url(socket, "event-123", "7d", "")
47+
assert url == "/fyi/events/event-123?range=7d"
48+
end
49+
50+
test "generates correct URL for nested scope" do
51+
socket = %{assigns: %{route_prefix: "/admin/fyi"}}
52+
url = InboxLive.event_url(socket, "event-456", "24h", "user.signup")
53+
assert url == "/admin/fyi/events/event-456?range=24h&type=user.signup"
54+
end
55+
56+
test "generates correct URL without event type filter" do
57+
socket = %{assigns: %{route_prefix: "/admin/dashboard/fyi"}}
58+
url = InboxLive.event_url(socket, "event-789", "1h", "")
59+
assert url == "/admin/dashboard/fyi/events/event-789?range=1h"
60+
end
61+
end
62+
63+
describe "build_url/3" do
64+
test "generates correct index URL for root-level scope" do
65+
socket = %{assigns: %{route_prefix: "/fyi"}}
66+
url = InboxLive.build_url(socket, "7d", "")
67+
assert url == "/fyi?range=7d"
68+
end
69+
70+
test "generates correct index URL for nested scope" do
71+
socket = %{assigns: %{route_prefix: "/admin/fyi"}}
72+
url = InboxLive.build_url(socket, "24h", "error.occurred")
73+
assert url == "/admin/fyi?range=24h&type=error.occurred"
74+
end
75+
76+
test "generates correct URL without event type" do
77+
socket = %{assigns: %{route_prefix: "/admin/dashboard/fyi"}}
78+
url = InboxLive.build_url(socket, "1h", "")
79+
assert url == "/admin/dashboard/fyi?range=1h"
80+
end
81+
end
82+
end

0 commit comments

Comments
 (0)