Skip to content

Commit

Permalink
feat: Scaffold v2 homepage (#1121)
Browse files Browse the repository at this point in the history
* Add navbar core component

* Add navbar to existing v2 index pages

* Add "switch to arrow v2" link to v1 homepage

* Scaffold /disruptionsv2 page

* "Cancel" link in disruption form now navigates to /disruptionsv2

* Test navbar component

* Use Controller.current_path instead of manually entering current path

* Guard against using navbar on a page without assigning a label for it

* Use sentence case in link text

* Use `for` instead of `:for` b/c it adds whitespace between generated elements 🥴

* Remove margin classes on links

* Run the new tests synchronously, in case that caused the issues for unrelated ones

* Fix casing of expected string in test

* Fix a mistake in test case "arrange" step

* Make "link" to current page in navbar unclickable to avoid accidental page reloads

* Change styling of unclickable navbar links

* Make template conditional logic a bit easier to follow? Hopefully?
  • Loading branch information
jzimbel-mbta authored Feb 13, 2025
1 parent 7a118f0 commit 516ccef
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 1 deletion.
1 change: 1 addition & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
@import "./footer.css";
@import "./header.css";
@import "./icon.css";
@import "./navbar.css";
@import "./notes.css";
@import "./shapes.css";
3 changes: 3 additions & 0 deletions assets/css/navbar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.navbar-current-page {
pointer-events: none;
}
53 changes: 53 additions & 0 deletions lib/arrow_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ defmodule ArrowWeb.CoreComponents do
"""
use Phoenix.Component

use Phoenix.VerifiedRoutes,
router: ArrowWeb.Router,
endpoint: ArrowWeb.Endpoint,
statics: ArrowWeb.static_paths()

alias Phoenix.LiveView.JS
use Gettext, backend: ArrowWeb.Gettext

Expand Down Expand Up @@ -580,6 +585,54 @@ defmodule ArrowWeb.CoreComponents do
"""
end

@doc """
Renders the Arrow navigation bar.
Bar renders as a col assuming it's inside a row, so that other content (e.g. search input)
can be placed alongside it.
"""
attr :page, :string, required: true
attr :create_disruption_permission?, :boolean, default: false

def navbar(assigns) do
pages = [
{~p"/disruptionsv2", "Disruptions"},
{~p"/shuttles", "Shuttle definitions"},
{~p"/shapes", "Shuttle shapes"},
{~p"/stops", "Shuttle stops"}
]

if assigns[:page] not in Enum.map(pages, &elem(&1, 0)) do
raise "navbar component used on an unrecognized page: #{assigns[:page]}"
end

homepage? = assigns[:page] == ~p"/disruptionsv2"
pages = if homepage?, do: tl(pages), else: pages

assigns = assign(assigns, pages: pages, homepage?: homepage?)

~H"""
<div class="col">
<%= if @homepage? do %>
<a :if={@create_disruption_permission?} class="btn btn-primary" href={~p"/disruptionsv2/new"}>
+ Create new
</a>
<a :if={not @create_disruption_permission?} class="btn btn-primary" href={~p"/disruptionsv2"}>
Disruptions
</a>
<% end %>
<%= for {page, label} <- @pages do %>
<% current? = page == @page %>
<a :if={current?} class="btn btn-secondary navbar-current-page" aria-disabled="true">
{label}
</a>
<a :if={not current?} class="btn btn-outline-secondary" href={page}>{label}</a>
<% end %>
<a class="btn btn-warning" href={~p"/"}>Switch to Arrow v1</a>
</div>
"""
end

@doc ~S"""
Renders a table with generic styling.
Expand Down
1 change: 1 addition & 0 deletions lib/arrow_web/controllers/disruption_html/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<%= if Permissions.authorize?(:create_disruption, @user) do %>
<a class="btn btn-primary" href="/disruptions/new">+ create new</a>
<% end %>
<a class="btn btn-warning" href="/disruptionsv2">Switch to Arrow v2</a>
</div>

<%= form_tag(Controller.current_path(@conn), method: "get", class: "col-3") do %>
Expand Down
13 changes: 13 additions & 0 deletions lib/arrow_web/controllers/disruption_v2_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule ArrowWeb.DisruptionV2Controller do
use ArrowWeb, :controller

alias ArrowWeb.Plug.Authorize
alias Plug.Conn

plug(Authorize, :view_disruption when action == :index)

@spec index(Conn.t(), Conn.params()) :: Conn.t()
def index(conn, _params) do
render(conn, "index.html", user: conn.assigns.current_user)
end
end
8 changes: 8 additions & 0 deletions lib/arrow_web/controllers/disruption_v2_html.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule ArrowWeb.DisruptionV2View do
use ArrowWeb, :html

alias Arrow.Permissions
alias Phoenix.Controller

embed_templates "disruption_v2_html/*"
end
15 changes: 15 additions & 0 deletions lib/arrow_web/controllers/disruption_v2_html/index.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="row my-3">
<.navbar
page={Controller.current_path(@conn)}
create_disruption_permission?={Permissions.authorize?(:create_disruption, @user)}
/>
<%= form_tag(Controller.current_path(@conn), method: "get", class: "col-3") do %>
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="search" />

<div class="input-group-append">
<button type="submit" class="btn btn-outline-secondary" title="search">🔎</button>
</div>
</div>
<% end %>
</div>
1 change: 1 addition & 0 deletions lib/arrow_web/controllers/shape_html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule ArrowWeb.ShapeView do
use ArrowWeb, :html
alias Arrow.Shuttles
alias Arrow.Shuttles.{Route, RouteStop, Shape, ShapesUpload}
alias Phoenix.Controller

embed_templates "shape_html/*"

Expand Down
4 changes: 4 additions & 0 deletions lib/arrow_web/controllers/shape_html/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<div class="row my-3">
<.navbar page={Controller.current_path(@conn)} />
</div>

<.header>
Listing Shapes
<:actions>
Expand Down
2 changes: 2 additions & 0 deletions lib/arrow_web/controllers/shuttle_html.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule ArrowWeb.ShuttleView do
use ArrowWeb, :html

alias Phoenix.Controller

embed_templates "shuttle_html/*"
end
4 changes: 4 additions & 0 deletions lib/arrow_web/controllers/shuttle_html/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<div class="row my-3">
<.navbar page={Controller.current_path(@conn)} />
</div>

<.header>
shuttles
<:actions>
Expand Down
2 changes: 2 additions & 0 deletions lib/arrow_web/controllers/stop_html.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule ArrowWeb.StopView do
use ArrowWeb, :html

alias Phoenix.Controller

embed_templates "stop_html/*"

def format_timestamp(%DateTime{} = dt) do
Expand Down
4 changes: 4 additions & 0 deletions lib/arrow_web/controllers/stop_html/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<div class="row my-3">
<.navbar page={Controller.current_path(@conn)} />
</div>

<.header>
Listing Stops
<:actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ defmodule ArrowWeb.DisruptionV2ViewLive do
</div>
<div class="w-25 mr-2">
<.link_button
href={~p"/"}
href={~p"/disruptionsv2"}
class="btn-outline-primary w-100"
data-confirm="Are you sure you want to cancel? All changes will be lost!"
>
Expand Down
1 change: 1 addition & 0 deletions lib/arrow_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ defmodule ArrowWeb.Router do
put("/disruptions/:id/row_status", DisruptionController, :update_row_status)
post("/disruptions/:id/notes", NoteController, :create)

get("/disruptionsv2", DisruptionV2Controller, :index)
live("/disruptionsv2/new", DisruptionV2ViewLive, :new)
live("/disruptionsv2/:id/edit", DisruptionV2ViewLive, :edit)

Expand Down
106 changes: 106 additions & 0 deletions test/arrow_web/components/core_components_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
defmodule ArrowWeb.CoreComponentsTest do
use ArrowWeb.ConnCase

import ArrowWeb.CoreComponents
import Phoenix.Component
import Phoenix.LiveViewTest

describe "navbar" do
test "link corresponding to current page has .btn-secondary and no href" do
assigns = %{page: "/shuttles"}

current_page_links =
~H"""
<.navbar page={@page} />
"""
|> rendered_to_string()
|> Floki.find("a.btn-secondary")

assert [current_page_link] = current_page_links

assert Floki.text(current_page_link) =~ "Shuttle definitions"
assert Floki.attribute(current_page_link, "href") == []
end

test "other v2 page links have .btn-outline-secondary and href" do
assigns = %{page: "/shuttles"}

secondary_links =
~H"""
<.navbar page={@page} />
"""
|> rendered_to_string()
|> Floki.find("a.btn-outline-secondary")

assert length(secondary_links) == 3
assert length(Floki.attribute(secondary_links, "href")) == 3
end

test "first link is to /disruptionsv2 when not on Disruptions page" do
assigns = %{page: "/shapes"}

hrefs =
~H"""
<.navbar page={@page} />
"""
|> rendered_to_string()
|> Floki.attribute("a", "href")

assert ["/disruptionsv2" | _] = hrefs
end

test "first link is to /disruptionsv2/new when on Disruptions page, with create permission" do
assigns = %{page: "/disruptionsv2", create_disruption_permission?: true}

hrefs =
~H"""
<.navbar page={@page} create_disruption_permission?={@create_disruption_permission?} />
"""
|> rendered_to_string()
|> Floki.attribute("a", "href")

assert ["/disruptionsv2/new" | _] = hrefs
end

test "first link is to /disruptionsv2 when on Disruptions page, without create permission" do
assigns = %{page: "/disruptionsv2"}

hrefs =
~H"""
<.navbar page={@page} />
"""
|> rendered_to_string()
|> Floki.attribute("a", "href")

assert ["/disruptionsv2" | _] = hrefs
end

test "renders link to v1 homepage with .btn-warning class" do
assigns = %{page: "/stops"}

warning_buttons =
~H"""
<.navbar page={@page} />
"""
|> rendered_to_string()
|> Floki.find(".btn-warning")

assert [warning_button] = warning_buttons

assert Floki.text(warning_button) == "Switch to Arrow v1"
end

test "raises an exception if @page is unrecognized" do
assigns = %{page: "/unknown_page"}

expect_msg = "navbar component used on an unrecognized page: /unknown_page"

ExUnit.Assertions.assert_raise(RuntimeError, expect_msg, fn ->
~H"""
<.navbar page={@page} />
"""
|> rendered_to_string()
end)
end
end
end

0 comments on commit 516ccef

Please sign in to comment.