Skip to content

Commit

Permalink
Merge branch 'main' into hp/test-out-bootstrap-reboot
Browse files Browse the repository at this point in the history
  • Loading branch information
hannahpurcell committed Aug 30, 2024
2 parents 6b8facf + a2fc46a commit 5bc0e23
Show file tree
Hide file tree
Showing 175 changed files with 2,048 additions and 859 deletions.
8 changes: 0 additions & 8 deletions .envrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ export SECRET_KEY_BASE=$(openssl rand -base64 48)
## Used by Erlang (only required in production)
# export RELEASE_COOKIE=

## AWS Cognito Authentication/authorization details (only required in production)
# export COGNITO_DOMAIN
# export COGNITO_CLIENT_ID
# export COGNITO_CLIENT_SECRET
# export COGNITO_USER_POOL_ID
# export COGNITO_AWS_REGION
# export GUARDIAN_SECRET_KEY

## CDN details (only required in production)
# export STATIC_SCHEME
# export STATIC_HOST
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ Skate is read-only, so it only needs to ingest data and present it. Skate uses t

## Authentication

Skate's authentication uses AWS Cognito as a middleman to manage interaction with Active Directory (using federated services). The actual login page the user interacts with is hosted by Active Directory, you use your same username and password as your email. We could someday add levels of authorization using groups in Cognito, but don't at this time.
Skate's authentication uses Keycloak as a middleman to manage interaction with Active Directory (using federated services). The actual login page the user interacts with is hosted by Active Directory, you use your same username and password as your email. We could someday add levels of authorization using groups in Keycloak and/or Active Directory, but don't at this time.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ WORKDIR /root

RUN curl https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem \
-o aws-cert-bundle.pem
RUN echo "51b107da46717aed974d97464b63f7357b220fe8737969db1492d1cae74b3947 aws-cert-bundle.pem" | sha256sum -c -
RUN echo "d72191eaa5d48fe2b6e044a0ae0b0e9f35e325b34b1ecab6ffe11d490d5cdb8f aws-cert-bundle.pem" | sha256sum -c -

# Add frontend assets compiled in node container, required by phx.digest
COPY --from=assets-builder /root/priv/static ./priv/static
Expand Down
2 changes: 1 addition & 1 deletion assets/css/_route_pill.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.c-route-pill {
width: 2.875rem;
min-width: 2.875rem;
height: 1.5rem;

border-radius: 0.8125rem;
Expand Down
3 changes: 2 additions & 1 deletion assets/css/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,20 @@ $vpp-location-padding: 1rem;
@import "data_status_banner";
@import "detours/detour_map";
@import "detours/detour_modal";
@import "detours/detour_table";
@import "directions_button";
@import "disconnected_modal";
@import "diversion_page";
@import "drawer_tab";
@import "dropdown";
@import "filter_accordion";
@import "garage_filter";
@import "grouped_autocomplete";
@import "icon_alert_circle";
@import "incoming_box";
@import "input_modal";
@import "ladder_page";
@import "logged_in_as";
@import "grouped_autocomplete";
@import "ladder";
@import "late_view";
@import "loading_modal";
Expand Down
2 changes: 1 addition & 1 deletion assets/css/bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
@import "../node_modules/bootstrap/scss/popover";
// @import "../node_modules/bootstrap/scss/progress";
@import "../node_modules/bootstrap/scss/spinners";
// @import "../node_modules/bootstrap/scss/tables";
@import "../node_modules/bootstrap/scss/tables";
// @import "../node_modules/bootstrap/scss/toasts";
@import "../node_modules/bootstrap/scss/tooltip";
// @import "../node_modules/bootstrap/scss/transitions";
Expand Down
17 changes: 17 additions & 0 deletions assets/css/detours/_detour_table.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.c-detours-table {
thead {
text-align: left;
font-weight: 600;
}
}

.c-detours-table__route-info-cell {
display: flex;
align-items: center;
}

.c-detours-table__route-info-text {
display: flex;
flex-direction: column;
margin-left: 10px;
}
6 changes: 6 additions & 0 deletions assets/css/utilities/_hideable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@
transition-delay: $transition-slide-duration;
}
}

.u-hide-for-mobile {
@media screen and (max-width: map-get($breakpoints, "max-mobile-landscape-tablet-portrait-width")) {
display: none;
}
}
72 changes: 72 additions & 0 deletions assets/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
enums,
Infer,
is,
never,
number,
object,
Struct,
StructError,
Expand Down Expand Up @@ -62,6 +64,7 @@ import {
UnfinishedDetourData,
unfinishedDetourFromData,
} from "./models/unfinishedDetour"
import { Snapshot } from "xstate"

export interface RouteData {
id: string
Expand Down Expand Up @@ -89,6 +92,15 @@ const checkResponseStatus = (response: Response) => {

const parseJson = (response: Response) => response.json() as unknown

/**
* @depcreated use {@linkcode apiCallResult}
*
* A small wrapper around fetch which checks for valid responses and parses
* JSON from the result body. It processes the resulting object with
* {@linkcode parser}
*
* If there is _any_ error, returns {@linkcode defaultResult}.
*/
export const apiCall = <T>({
url,
parser,
Expand All @@ -106,6 +118,17 @@ export const apiCall = <T>({
.then(({ data: data }: { data: any }) => parser(data))
.catch(() => defaultResult)

/**
* @depcreated use {@linkcode apiCallResult}
*
* A slightly larger (than {@linkcode apiCall}) wrapper around
* {@linkcode fetch} which checks for valid responses and then parses JSON from
* the body, and asserts it's validity with `superstruct`.
*
* It then transforms the input with {@linkcode parser}
*
* If there are any errors, returns {@linkcode defaultResult}
*/
export const checkedApiCall = <T, U>({
url,
dataStruct,
Expand Down Expand Up @@ -134,6 +157,14 @@ export const checkedApiCall = <T, U>({
return defaultResult
})

/**
* @depcreated use {@linkcode apiCallResult}
*
* A wrapper around {@linkcode fetch} which returns a {@linkcode FetchResult}.
*
* This does mainly the same thing as {@linkcode checkedApiCall} but returns
* errors and successes separately using {@linkcode FetchResult}.
*/
export const apiCallWithError = <T, U>({
url,
dataStruct,
Expand All @@ -160,6 +191,30 @@ export const apiCallWithError = <T, U>({
return fetchError()
})

/**
* A wrapper around {@linkcode fetch} which returns a {@linkcode Result}.
*
* This function returns a {@linkcode Result} so that it's easy to differentiate
* between different error states.
*
* For example, previous implementations,
* e.g. {@linkcode checkedApiCall} and {@linkcode apiCall}, provided a
* `defaultResult` parameter which was returned if there was _any_ issue;
* If a successful {@linkcode fetch} to an endpoint _also_ returned the same
* value as `defaultResult`, say `null`, then there isn't a way to tell if the
* `null` was because of an error or because of a successful request.
* This _also_ happened if there was an unrelated error when fetching, so there
* was not an easy way to tell the difference between an errored endpoint or an
* errored fetch call.
*
* Diverging from {@linkcode apiCall}, this does not handle errors such as
* network issues and deals only with json response bodies. That is left up to
* the caller to add a `.catch` handler to the returned {@linkcode Promise},
* because there may not be a generic way that those kind of errors should be
* handled. (This API could be opinionated or extended to return something like
* `Promise<Result<Result<T, E>, FetchError>>`, but instead that is left up to
* callers to implement instead of assuming any requirements.
*/
export const apiCallResult = async <T, E>(
url: Parameters<typeof fetch>[0],
OkStruct: Struct<T, unknown>,
Expand All @@ -168,8 +223,13 @@ export const apiCallResult = async <T, E>(
): Promise<Result<T, E>> =>
fetch(url, requestInit)
.then(async (response) => {
// If the fetch does not error and returns something from the endpoint,
// parse as json.
const json: unknown = await response.json()

// Then check if the response is `ok` and try to return `Ok(OkStruct)`
// Otherwise, return `Err(ErrStruct)` and attempt to return the data
// according to JSONAPI specifications
if (response.ok && is(json, object({ data: any() }))) {
return Ok(create(json.data, OkStruct))
} else {
Expand Down Expand Up @@ -441,6 +501,18 @@ export const putRouteTabs = (routeTabs: RouteTab[]): Promise<Response> =>
body: JSON.stringify({ route_tabs: routeTabs }),
})

export const putDetourUpdate = (
snapshot: Snapshot<unknown>
): Promise<Result<number, never>> =>
apiCallResult(`/api/detours/update_snapshot`, number(), never(), {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-csrf-token": getCsrfToken(),
},
body: JSON.stringify({ snapshot: snapshot }),
})

const getCsrfToken = (): string => appData()?.csrfToken || ""

export const nullableParser =
Expand Down
4 changes: 2 additions & 2 deletions assets/src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { usePanelStateFromStateDispatchContext } from "../hooks/usePanelState"
import PropertiesPanel from "./propertiesPanel"
import { isGhost, isVehicle } from "../models/vehicle"
import { TabMode } from "./propertiesPanel/tabPanels"
import { DummyDetourPage } from "./dummyDetourPage"
import { DetourListPage } from "./detourListPage"
import inTestGroup, { TestGroups } from "../userInTestGroup"
import { MinimalLadderPage } from "./minimalLadderPage"
import { MinimalLadder } from "./minimalLadder"
Expand Down Expand Up @@ -107,7 +107,7 @@ export const AppRoutes = () => {
/>
<BrowserRoute path="/settings" element={<SettingsPage />} />
{inTestGroup(TestGroups.DetoursList) && (
<BrowserRoute path="/detours" element={<DummyDetourPage />} />
<BrowserRoute path="/detours" element={<DetourListPage />} />
)}
</Route>
<Route
Expand Down
103 changes: 103 additions & 0 deletions assets/src/components/detourListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useState } from "react"
import { DetoursTable } from "./detoursTable"
import userInTestGroup, { TestGroups } from "../userInTestGroup"
import { Button } from "react-bootstrap"
import { PlusSquare } from "../helpers/bsIcons"
import { DetourModal } from "./detours/detourModal"

export interface Detour {
route: string
direction: string
name: string
intersection: string
activeSince: number
}

export const DetourListPage = () => {
const fakeData = [
{
route: "45",
direction: "Outbound",
name: "Franklin Park via Ruggles Station",
intersection: "John F. Kennedy St & Memorial Drive",
activeSince: 1722372950,
},
{
route: "83",
direction: "Inbound",
name: "Central Square",
intersection: "Pearl Street & Clearwell Ave",
activeSince: 1722361948,
},
{
route: "83",
direction: "Outbound",
name: "Rindge Ave",
intersection: "Pearl Street & Clearwell Ave",
activeSince: 1721361948,
},
{
route: "45",
direction: "Outbound",
name: "Franklin Park via Ruggles Station",
intersection: "John F. Kennedy St & Memorial Drive",
activeSince: 1722372950,
},
{
route: "83",
direction: "Inbound",
name: "Central Square",
intersection: "Pearl Street & Clearwell Ave",
activeSince: 1722361948,
},
{
route: "83",
direction: "Outbound",
name: "Rindge Ave",
intersection: "Pearl Street & Clearwell Ave",
activeSince: 1721361948,
},
{
route: "45",
direction: "Outbound",
name: "Franklin Park via Ruggles Station",
intersection: "John F. Kennedy St & Memorial Drive",
activeSince: 1722372950,
},
{
route: "83",
direction: "Inbound",
name: "Central Square",
intersection: "Pearl Street & Clearwell Ave",
activeSince: 1722361948,
},
{
route: "83",
direction: "Outbound",
name: "Rindge Ave",
intersection: "Pearl Street & Clearwell Ave",
activeSince: 1721361948,
},
]

const [showDetourModal, setShowDetourModal] = useState(false)

return (
<div className="h-100 overflow-y-auto">
{userInTestGroup(TestGroups.DetoursPilot) && (
<Button className="icon-link" onClick={() => setShowDetourModal(true)}>
<PlusSquare />
Add detour
</Button>
)}
<DetoursTable data={fakeData} />
{showDetourModal && (
<DetourModal
onClose={() => setShowDetourModal(false)}
show
originalRoute={{}}
/>
)}
</div>
)
}
27 changes: 27 additions & 0 deletions assets/src/components/detours/activeDetourPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react"
import { Panel } from "./diversionPage"
import { Button } from "react-bootstrap"

export const ActiveDetourPanel = ({
onDeactivateDetour,
}: {
onDeactivateDetour: () => void
}) => (
<Panel as="article">
<Panel.Header className="">
<h1 className="c-diversion-panel__h1 my-3">Active Detour</h1>
</Panel.Header>

<Panel.Body className="d-flex flex-column">
<Panel.Body.Footer>
<Button
className="m-3 flex-grow-1"
variant="missed-stop"
onClick={onDeactivateDetour}
>
Deactivate Detour
</Button>
</Panel.Body.Footer>
</Panel.Body>
</Panel>
)
Loading

0 comments on commit 5bc0e23

Please sign in to comment.