From 15fc65e25bd27b31c8c418c3201bd287f105492c Mon Sep 17 00:00:00 2001 From: Chris Gregori Date: Sun, 28 Dec 2025 11:51:20 +0000 Subject: [PATCH] Fix nested route support for InboxLive --- CHANGELOG.md | 6 +++ lib/fyi/web/inbox_live.ex | 45 ++++++++++++++---- test/fyi/web/inbox_live_test.exs | 82 ++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 test/fyi/web/inbox_live_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 555414a..381dfcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **Nested route support** - InboxLive now correctly handles URLs when mounted in nested scopes (e.g., `/admin/fyi` instead of just `/fyi`) + - Event detail URLs now respect the route prefix where the LiveView is mounted + - Navigation between index and detail views works correctly regardless of scope nesting + ## [1.0.1] - 2025-12-27 ### Added diff --git a/lib/fyi/web/inbox_live.ex b/lib/fyi/web/inbox_live.ex index 71ff317..a4dbd37 100644 --- a/lib/fyi/web/inbox_live.ex +++ b/lib/fyi/web/inbox_live.ex @@ -35,13 +35,17 @@ if Code.ensure_loaded?(Phoenix.LiveView) do end @impl true - def handle_params(params, _uri, socket) do + def handle_params(params, uri, socket) do time_range = params["range"] || "7d" event_type = params["type"] || "" event_id = params["id"] + # Extract route prefix from URI + route_prefix = extract_route_prefix_from_uri(uri) + socket = socket + |> assign(:route_prefix, route_prefix) |> assign(:time_range, time_range) |> assign(:event_type, event_type) |> load_event_types() @@ -71,24 +75,39 @@ if Code.ensure_loaded?(Phoenix.LiveView) do @impl true def handle_event("time_range", %{"range" => range}, socket) do - {:noreply, push_patch(socket, to: build_url(range, socket.assigns.event_type))} + {:noreply, push_patch(socket, to: build_url(socket, range, socket.assigns.event_type))} end @impl true def handle_event("event_type", %{"type" => type}, socket) do - {:noreply, push_patch(socket, to: build_url(socket.assigns.time_range, type))} + {:noreply, push_patch(socket, to: build_url(socket, socket.assigns.time_range, type))} end @impl true def handle_event("close_detail", _, socket) do {:noreply, - push_patch(socket, to: build_url(socket.assigns.time_range, socket.assigns.event_type))} + push_patch(socket, + to: build_url(socket, socket.assigns.time_range, socket.assigns.event_type) + )} + end + + @doc false + def extract_route_prefix_from_uri(uri) do + # Extract the route prefix from the URI + # For example: "http://localhost:4000/admin/fyi?range=7d" -> "/admin/fyi" + # "http://localhost:4000/fyi/events/123?range=7d" -> "/fyi" + uri + |> URI.parse() + |> Map.get(:path, "/fyi") + |> String.split("/events/") + |> List.first() end - defp build_url(range, type) do + @doc false + def build_url(socket, range, type) do params = [{"range", range}] params = if type != "", do: params ++ [{"type", type}], else: params - "/fyi?" <> URI.encode_query(params) + "#{socket.assigns.route_prefix}?" <> URI.encode_query(params) end @impl true @@ -697,7 +716,7 @@ if Code.ensure_loaded?(Phoenix.LiveView) do <% else %> <%= for event <- @events do %> - <.link patch={event_url(event.id, @time_range, @event_type)} class="fyi-event-row"> + <.link patch={event_url(assigns, event.id, @time_range, @event_type)} class="fyi-event-row"> @@ -849,10 +868,18 @@ if Code.ensure_loaded?(Phoenix.LiveView) do end end - defp event_url(event_id, range, type) do + @doc false + def event_url(socket_or_assigns, event_id, range, type) do + route_prefix = + case socket_or_assigns do + %{assigns: %{route_prefix: prefix}} -> prefix + %{route_prefix: prefix} -> prefix + _ -> "/fyi" + end + params = [{"range", range}] params = if type != "", do: params ++ [{"type", type}], else: params - "/fyi/events/#{event_id}?" <> URI.encode_query(params) + "#{route_prefix}/events/#{event_id}?" <> URI.encode_query(params) end defp time_range_since(range) do diff --git a/test/fyi/web/inbox_live_test.exs b/test/fyi/web/inbox_live_test.exs new file mode 100644 index 0000000..3785786 --- /dev/null +++ b/test/fyi/web/inbox_live_test.exs @@ -0,0 +1,82 @@ +defmodule FYI.Web.InboxLiveTest do + use ExUnit.Case, async: true + + alias FYI.Web.InboxLive + + describe "extract_route_prefix_from_uri/1" do + test "extracts route prefix from root-level /fyi path" do + uri = "http://localhost:4000/fyi?range=7d" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/fyi" + end + + test "extracts route prefix from nested /admin/fyi path" do + uri = "http://localhost:4000/admin/fyi?range=7d" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi" + end + + test "extracts route prefix from deeply nested path" do + uri = "http://localhost:4000/admin/dashboard/fyi?range=7d" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/dashboard/fyi" + end + + test "extracts route prefix from event detail URL at root level" do + uri = "http://localhost:4000/fyi/events/123?range=7d" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/fyi" + end + + test "extracts route prefix from event detail URL in nested scope" do + uri = "http://localhost:4000/admin/fyi/events/456?range=7d&type=user" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi" + end + + test "handles URI without query params" do + uri = "http://localhost:4000/admin/fyi" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi" + end + + test "handles URI with trailing slash" do + uri = "http://localhost:4000/admin/fyi/" + assert InboxLive.extract_route_prefix_from_uri(uri) == "/admin/fyi/" + end + end + + describe "event_url/4" do + test "generates correct URL for root-level scope" do + socket = %{assigns: %{route_prefix: "/fyi"}} + url = InboxLive.event_url(socket, "event-123", "7d", "") + assert url == "/fyi/events/event-123?range=7d" + end + + test "generates correct URL for nested scope" do + socket = %{assigns: %{route_prefix: "/admin/fyi"}} + url = InboxLive.event_url(socket, "event-456", "24h", "user.signup") + assert url == "/admin/fyi/events/event-456?range=24h&type=user.signup" + end + + test "generates correct URL without event type filter" do + socket = %{assigns: %{route_prefix: "/admin/dashboard/fyi"}} + url = InboxLive.event_url(socket, "event-789", "1h", "") + assert url == "/admin/dashboard/fyi/events/event-789?range=1h" + end + end + + describe "build_url/3" do + test "generates correct index URL for root-level scope" do + socket = %{assigns: %{route_prefix: "/fyi"}} + url = InboxLive.build_url(socket, "7d", "") + assert url == "/fyi?range=7d" + end + + test "generates correct index URL for nested scope" do + socket = %{assigns: %{route_prefix: "/admin/fyi"}} + url = InboxLive.build_url(socket, "24h", "error.occurred") + assert url == "/admin/fyi?range=24h&type=error.occurred" + end + + test "generates correct URL without event type" do + socket = %{assigns: %{route_prefix: "/admin/dashboard/fyi"}} + url = InboxLive.build_url(socket, "1h", "") + assert url == "/admin/dashboard/fyi?range=1h" + end + end +end