From 026b5815d1a2bd8e3fb1521e46926652121e7b99 Mon Sep 17 00:00:00 2001 From: Josh Larson Date: Thu, 4 Jun 2026 15:23:36 -0400 Subject: [PATCH 1/2] feat(dev): Update the schedule finder preview page to make departures pages easier to get to --- .../components/preview_components.ex | 165 +++++++++++++++++ lib/dotcom_web/live/preview_live.ex | 6 +- .../live/schedule_finder_picker_live.ex | 168 ++++++++++++++++++ lib/dotcom_web/router.ex | 1 + 4 files changed, 337 insertions(+), 3 deletions(-) create mode 100644 lib/dotcom_web/components/preview_components.ex create mode 100644 lib/dotcom_web/live/schedule_finder_picker_live.ex diff --git a/lib/dotcom_web/components/preview_components.ex b/lib/dotcom_web/components/preview_components.ex new file mode 100644 index 0000000000..5858b4b817 --- /dev/null +++ b/lib/dotcom_web/components/preview_components.ex @@ -0,0 +1,165 @@ +defmodule DotcomWeb.PreviewComponents do + @moduledoc """ + Some silly components to be used in non-rider-facing preview explorations + """ + + use DotcomWeb, :component + + attr :clear_button_click, :string, required: true + attr :class, :string, default: "" + attr :style, :string, default: "" + slot :inner_block + + defp banner(assigns) do + ~H""" +
+
+
+ {render_slot(@inner_block)} +
+ <.icon class="size-5" name="circle-xmark" /> +
+
+
+
+ """ + end + + attr :route, Routes.Route, required: true + + def route_banner(assigns) do + ~H""" + <.banner + style={"background-color: ##{@route.color}; color: #{text_color(@route)}; fill: #{text_color(@route)};"} + clear_button_click="clear-route" + > + {@route.name} + + """ + end + + attr :direction_id, :string, required: true + attr :route, Routes.Route, required: true + + def direction_banner(assigns) do + ~H""" + <.banner clear_button_click="clear-direction" class="bg-gray-lightest"> + {direction_description(@route, @direction_id)} + + """ + end + + attr :stop, Stops.Stop, required: true + + def stop_banner(assigns) do + ~H""" + <.banner clear_button_click="clear-stop" class="bg-charcoal-10 text-white fill-white"> +
+ {@stop.name} + {@stop.id} +
+ + """ + end + + attr :route, Routes.Route, default: nil + attr :routes, :list, required: true + slot :inner_block + + def route_picker_or(%{route: nil} = assigns) do + ~H""" +
+
+ +
+
+ """ + end + + def route_picker_or(assigns) do + ~H""" + {render_slot(@inner_block)} + """ + end + + attr :route, Routes.Route, required: true + attr :direction_id, :string, required: true + slot :inner_block + + def direction_picker_or(%{direction_id: nil} = assigns) do + ~H""" +
+
+ +
+
+ """ + end + + def direction_picker_or(assigns) do + ~H""" + {render_slot(@inner_block)} + """ + end + + attr :stop, Stops.Stop, default: nil + attr :stops, :list, required: true + slot :inner_block + + def stop_picker_or(%{stop: nil} = assigns) do + ~H""" +
+
+ +
+
+ """ + end + + def stop_picker_or(assigns) do + ~H""" + {render_slot(@inner_block)} + """ + end + + defp direction_description(route, direction_id) do + "#{route.direction_names[direction_id]} towards #{route.direction_destinations[direction_id]}" + end + + defp text_color(route) do + if(route.type == 3 and not String.contains?(route.name, "SL"), do: "black", else: "white") + end +end diff --git a/lib/dotcom_web/live/preview_live.ex b/lib/dotcom_web/live/preview_live.ex index 0fc7a7b2a9..e24cb4bea6 100644 --- a/lib/dotcom_web/live/preview_live.ex +++ b/lib/dotcom_web/live/preview_live.ex @@ -7,15 +7,15 @@ defmodule DotcomWeb.PreviewLive do use DotcomWeb, :live_view alias DotcomWeb.Router.Helpers - alias DotcomWeb.ScheduleFinderLive + alias DotcomWeb.ScheduleFinderPickerLive alias DotcomWeb.StopMapLive alias Phoenix.LiveView @pages [ %{ - arguments: [[route_id: "Red", direction_id: "0"]], + arguments: [], icon_name: "icon-realtime-tracking", - module: ScheduleFinderLive, + module: ScheduleFinderPickerLive, title: "Schedule Finder 2.0" }, %{ diff --git a/lib/dotcom_web/live/schedule_finder_picker_live.ex b/lib/dotcom_web/live/schedule_finder_picker_live.ex new file mode 100644 index 0000000000..c15bd92e12 --- /dev/null +++ b/lib/dotcom_web/live/schedule_finder_picker_live.ex @@ -0,0 +1,168 @@ +defmodule DotcomWeb.ScheduleFinderPickerLive do + @moduledoc """ + A page that allows us to easily navigate to the new Schedule Finder page + """ + + use DotcomWeb, :live_view + + import DotcomWeb.PreviewComponents, + only: [ + direction_banner: 1, + direction_picker_or: 1, + route_banner: 1, + route_picker_or: 1, + stop_banner: 1, + stop_picker_or: 1 + ] + + alias Phoenix.LiveView + + @impl LiveView + def mount(_params, _session, socket) do + {:ok, + socket + |> assign(:routes, Routes.Repo.all()) + |> assign(:subscribed?, false) + |> assign(:predictions_list_by_snapshot, []) + |> assign(:predictions_map_by_events, %{}) + |> assign(:predictions_list_by_events, []) + |> assign(:prediction_events, [])} + end + + @impl LiveView + def handle_params(params, _uri, socket) do + route_id = Map.get(params, "route_id") + + direction_id = + params + |> Map.get("direction_id") + |> case do + nil -> nil + str when is_binary(str) -> String.to_integer(str) + end + + stop_id = Map.get(params, "stop_id") + + {:noreply, + socket + |> assign_route(route_id) + |> assign_direction(direction_id) + |> assign_stop(stop_id)} + end + + defp assign_route(socket, nil) do + socket + |> assign(:route_id, nil) + |> assign(:route, nil) + end + + defp assign_route(socket, route_id) do + socket + |> assign(:route_id, route_id) + |> assign(:route, Routes.Repo.get(route_id)) + end + + defp assign_direction(socket, nil) do + socket + |> assign(:direction_id, nil) + |> assign(:stops, nil) + end + + defp assign_direction(socket, direction_id) do + stops = + Stops.Repo.by_route(socket.assigns.route_id, direction_id) + + socket + |> assign(:direction_id, direction_id) + |> assign(:stops, stops) + end + + defp assign_stop(socket, nil) do + socket + |> assign(:stop_id, nil) + |> assign(:stop, nil) + end + + defp assign_stop(socket, stop_id) do + socket + |> assign(:stop_id, stop_id) + |> assign(:stop, Stops.Repo.get(stop_id)) + end + + @impl LiveView + def render(assigns) do + ~H""" + <.route_picker_or route={@route} routes={@routes}> + <.route_banner route={@route} /> + + <.direction_picker_or route={@route} direction_id={@direction_id}> + <.direction_banner route={@route} direction_id={@direction_id} /> + + <.stop_picker_or + stop={@stop} + stops={@stops} + > + <.stop_banner stop={@stop} /> + +
+ <.link + navigate={ + ~p"/departures?route_id=#{@route_id}&direction_id=#{@direction_id}&stop_id=#{@stop_id}" + } + target="_blank" + class="bg-gray-lightest text-black font-bold p-2 rounded" + > + See Departures Page + +
+ + + + """ + end + + @impl LiveView + def handle_event("clear-route", _params, socket) do + {:noreply, socket |> push_patch(to: ~p"/preview/schedule-finder-picker")} + end + + def handle_event("clear-direction", _params, socket) do + route_id = socket.assigns.route_id + + params = %{route_id: route_id} + {:noreply, socket |> push_patch(to: ~p"/preview/schedule-finder-picker?#{params}")} + end + + def handle_event("clear-stop", _params, socket) do + route_id = socket.assigns.route_id + direction_id = socket.assigns.direction_id + + params = %{route_id: route_id, direction_id: direction_id} + {:noreply, socket |> push_patch(to: ~p"/preview/schedule-finder-picker?#{params}")} + end + + def handle_event("select-direction", %{"direction-id" => direction_id}, socket) do + route_id = socket.assigns.route_id + + params = %{route_id: route_id, direction_id: direction_id} + {:noreply, socket |> push_patch(to: ~p"/preview/schedule-finder-picker?#{params}")} + end + + def handle_event("select-route", %{"route-id" => route_id}, socket) do + params = %{route_id: route_id} + {:noreply, socket |> push_patch(to: ~p"/preview/schedule-finder-picker?#{params}")} + end + + def handle_event("select-stop", %{"stop-id" => stop_id}, socket) do + route_id = socket.assigns.route_id + direction_id = socket.assigns.direction_id + + params = %{route_id: route_id, direction_id: direction_id, stop_id: stop_id} + {:noreply, socket |> push_patch(to: ~p"/preview/schedule-finder-picker?#{params}")} + end + + @impl LiveView + def terminate(_reason, _socket) do + :ok + end +end diff --git a/lib/dotcom_web/router.ex b/lib/dotcom_web/router.ex index 84f1880174..de9feebb5a 100644 --- a/lib/dotcom_web/router.ex +++ b/lib/dotcom_web/router.ex @@ -334,6 +334,7 @@ defmodule DotcomWeb.Router do live_session :default, layout: {DotcomWeb.LayoutView, :preview} do live "/", PreviewLive + live "/schedule-finder-picker", ScheduleFinderPickerLive live "/schedules/bostonstadium", WorldCupTimetableLive live "/stop-map", StopMapLive end From 1bddaf6c9f8ea0c4166d52f6e662f2ea0a527486 Mon Sep 17 00:00:00 2001 From: Josh Larson Date: Fri, 5 Jun 2026 15:20:12 -0400 Subject: [PATCH 2/2] cleanup: Remove `assign` statements for fields that aren't needed --- lib/dotcom_web/live/schedule_finder_picker_live.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/dotcom_web/live/schedule_finder_picker_live.ex b/lib/dotcom_web/live/schedule_finder_picker_live.ex index c15bd92e12..129e09f6df 100644 --- a/lib/dotcom_web/live/schedule_finder_picker_live.ex +++ b/lib/dotcom_web/live/schedule_finder_picker_live.ex @@ -21,12 +21,7 @@ defmodule DotcomWeb.ScheduleFinderPickerLive do def mount(_params, _session, socket) do {:ok, socket - |> assign(:routes, Routes.Repo.all()) - |> assign(:subscribed?, false) - |> assign(:predictions_list_by_snapshot, []) - |> assign(:predictions_map_by_events, %{}) - |> assign(:predictions_list_by_events, []) - |> assign(:prediction_events, [])} + |> assign(:routes, Routes.Repo.all())} end @impl LiveView