Skip to content

Commit

Permalink
feat: navigation menu + wordmark (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
mathcolo authored Sep 6, 2024
1 parent 368b8d0 commit c6fcaa0
Show file tree
Hide file tree
Showing 23 changed files with 415 additions and 49 deletions.
16 changes: 8 additions & 8 deletions js/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { reload } from "../browser";
import { paths } from "../paths";
import { Header } from "./header";
import { Help } from "./help";
import { Home } from "./home";
import { List } from "./operatorSignIn/list";
import { HelpMenu, Menu } from "./menus";
import { captureException } from "@sentry/react";
import { ReactElement, useEffect } from "react";
import {
Expand All @@ -29,7 +29,7 @@ const ErrorBoundary = (): ReactElement => {
.
</li>
<li>
<a href="/">Go back to the Orbit home page</a>.
<a href={paths.root}>Go back to the Orbit home page</a>.
</li>
<li>Try again in a few minutes.</li>
<li>Call the IT help desk. x5761</li>
Expand All @@ -55,16 +55,16 @@ const router = createBrowserRouter([
),
children: [
{
path: "/",
path: paths.root,
element: <Home />,
},
{
path: "/list",
element: <List line="blue" />,
path: paths.menu,
element: <Menu />,
},
{
path: "/help",
element: <Help />,
path: paths.help,
element: <HelpMenu />,
},
],
},
Expand Down
30 changes: 26 additions & 4 deletions js/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { paths } from "../paths";
import { className } from "../util/dom";
import { Link, useLocation } from "react-router-dom";

export const Header = () => {
const currentLocation = useLocation();
const menuSelected =
currentLocation.pathname === paths.menu ||
currentLocation.pathname === paths.help;

return (
<div className="w-full bg-gray-200 p-2 flex justify-between">
<img src="/images/logo.svg" alt="MBTA" className="w-8" />
<a href="/logout" className="underline p-1">
Log out
</a>
<Link to={paths.root}>
<img src="/images/logo.svg" alt="MBTA" className="w-44" />
</Link>
<Link to={paths.menu}>
<button
className={className([
"border border-black bg-gray-200 rounded w-8 h-8",
menuSelected && "invert border-white",
])}
>
<img
src="/images/menu.svg"
alt="Menu"
className="w-full h-full text-black"
/>
</button>
</Link>
</div>
);
};
32 changes: 0 additions & 32 deletions js/components/help.tsx

This file was deleted.

124 changes: 124 additions & 0 deletions js/components/menus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { IconName } from "../icons";
import { paths } from "../paths";
import { className } from "../util/dom";
import { getMetaContent } from "../util/metadata";
import { ReactElement } from "react";
import { Link } from "react-router-dom";

const MenuLink = ({
to,
icon,
iconExtraClassName,
reload,
newTab,
children,
}: {
to: string;
icon?: IconName;
iconExtraClassName?: string;
reload?: boolean;
newTab?: boolean;
children: string;
}): ReactElement => (
<Link
to={to}
reloadDocument={reload}
target={newTab ? "_blank" : undefined}
rel={newTab ? "noopener noreferrer" : undefined}
>
<button className="block font-semibold mx-auto w-full rounded border border-gray-300 bg-none hover:bg-gray-200 p-1 mb-6">
{icon && (
<img
src={`/images/${icon}.svg`}
// Per MDN re: alt text:
// > If the image doesn't require a fallback (such as for an image which is decorative or an advisory icon
// of minimal importance), you may specify an empty string ("")
alt={""}
className={className(["inline", iconExtraClassName])}
/>
)}
{children}
</button>
</Link>
);

export const Menu = (): ReactElement => {
const name = getMetaContent("userName");
const email = getMetaContent("userEmail");

return (
<main className="w-5/6 mx-auto mt-4 max-w-[400px]">
<h2 className="text-2xl mb-6">Hi, {name ?? email}</h2>

<MenuLink icon="help" iconExtraClassName="h-7 w-7" to={paths.help}>
Help
</MenuLink>
<MenuLink
icon="feedback"
iconExtraClassName="h-7 w-7"
newTab={true}
to="https://form.asana.com/?k=NS0FKd0bBpHpURLbD3jmeg&d=15492006741476"
>
Send Feedback
</MenuLink>
<hr className="mx-auto my-8 w-full" />
<MenuLink
icon={"sign-out"}
iconExtraClassName="h-5 w-5 mr-1"
to={paths.logout}
reload={true}
>
Logout
</MenuLink>
</main>
);
};

export const HelpMenu = (): ReactElement => {
return (
<main className="w-5/6 mx-auto mt-4 max-w-[400px]">
<Link
className="mb-6 tracking-widest h-full w-16 text-sm font-semibold uppercase no-underline"
to={paths.menu}
>
<img
src={`/images/back.svg`}
alt={"Back"}
className="w-5 inline -translate-y-0.5"
/>
Back
</Link>

<h2 className="text-2xl my-8">How can we help?</h2>

<MenuLink
icon="info"
iconExtraClassName="w-4 mr-2"
newTab={true}
to="https://www.mbta.com/orbit-user-guide"
>
User Guide
</MenuLink>
<MenuLink
icon="clipboard"
iconExtraClassName="w-3 mr-2"
newTab={true}
to="https://www.mbta.com/orbit-training"
>
Training Materials
</MenuLink>
<hr className="mx-auto my-8 w-full" />
<div>
For more support, contact{" "}
<Link className="underline" to={"mailto:[email protected]"}>
[email protected]
</Link>{" "}
or{" "}
<Link className="underline" to={"tel:+1-617-222-5761"}>
617-222-5761
</Link>{" "}
and mention that you are having an issue with Orbit.
</div>
</main>
);
};
10 changes: 10 additions & 0 deletions js/icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type IconName =
| "back"
| "clipboard"
| "feedback"
| "help"
| "info"
| "logo"
| "menu"
| "shield"
| "sign-out";
6 changes: 6 additions & 0 deletions js/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const paths = {
root: "/",
menu: "/menu",
help: "/help",
logout: "/logout",
} as const;
36 changes: 36 additions & 0 deletions js/test/components/menus.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { HelpMenu, Menu } from "../../components/menus";
import { render } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";

describe("menu", () => {
test("has links", () => {
const view = render(
<MemoryRouter>
<Menu />
</MemoryRouter>,
);
expect(view.getAllByRole("button")).toHaveLength(3);
expect(view.getByRole("button", { name: "Help" })).toBeInTheDocument();
expect(
view.getByRole("button", { name: "Send Feedback" }),
).toBeInTheDocument();
expect(view.getByRole("button", { name: "Logout" })).toBeInTheDocument();
});
});

describe("help menu", () => {
test("has links", () => {
const view = render(
<MemoryRouter>
<HelpMenu />
</MemoryRouter>,
);
expect(view.getAllByRole("button")).toHaveLength(2);
expect(
view.getByRole("button", { name: "User Guide" }),
).toBeInTheDocument();
expect(
view.getByRole("button", { name: "Training Materials" }),
).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions js/util/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type MetaDataKey =
| "sentryDsn"
| "environment"
| "userEmail"
| "userName"
| "release";

export const getMetaContent = (field: MetaDataKey): string | null => {
Expand Down
28 changes: 28 additions & 0 deletions lib/orbit/authentication/user.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
defmodule Orbit.Authentication.User do
alias Orbit.Authentication.UserPermission
alias Orbit.Employee
alias Orbit.Repo

use Ecto.Schema
import Ecto.Changeset
import Ecto.Query

@type t :: %__MODULE__{
id: integer(),
Expand Down Expand Up @@ -38,4 +42,28 @@ defmodule Orbit.Authentication.User do
name: :users_email_index
)
end

@spec get_display_name(t()) :: String.t() | nil
def get_display_name(struct) do
names =
List.first(
Repo.all(
from(e in Employee,
where: e.email == ^struct.email,
select: %{
first_name: e.first_name,
preferred_first: e.preferred_first,
last_name: e.last_name
}
)
),
nil
)

case names do
nil -> nil
%{preferred_first: nil, first_name: fname, last_name: lname} -> "#{fname} #{lname}"
_ -> "#{names.preferred_first} #{names.last_name}"
end
end
end
3 changes: 3 additions & 0 deletions lib/orbit_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<%= if @conn.assigns[:email] do %>
<meta name="userEmail" content={@email} />
<% end %>
<%= if @conn.assigns[:name] do %>
<meta name="userName" content={@name} />
<% end %>

<title><%= assigns[:page_title] || "Orbit" %></title>
<link rel="stylesheet" href={~p"/assets/app.css"} />
Expand Down
2 changes: 2 additions & 0 deletions lib/orbit_web/plugs/require_login.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule OrbitWeb.Plugs.RequireLogin do
@behaviour Plug

alias Orbit.Authentication.User
alias OrbitWeb.Auth.Auth

@impl Plug
Expand All @@ -19,6 +20,7 @@ defmodule OrbitWeb.Plugs.RequireLogin do

conn
|> Plug.Conn.assign(:email, user.email)
|> Plug.Conn.assign(:name, User.get_display_name(user))
|> Plug.Conn.assign(:logged_in_user, user)
else
conn
Expand Down
2 changes: 1 addition & 1 deletion lib/orbit_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule OrbitWeb.Router do
# Routes that should be handled by React
# Avoid using a wildcard to prevent invalid 200 responses
get "/", ReactAppController, :home
get "/list", ReactAppController, :home
get "/menu", ReactAppController, :home
get "/help", ReactAppController, :home
get "/logout", AuthController, :logout

Expand Down
9 changes: 9 additions & 0 deletions priv/static/images/back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions priv/static/images/clipboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c6fcaa0

Please sign in to comment.