Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ts/notifications): create notification card for detours #2819

Merged
merged 11 commits into from
Oct 1, 2024
10 changes: 9 additions & 1 deletion assets/src/components/notificationBellIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { joinClasses } from "../helpers/dom"
import { NotificationBellIcon as NotificationBellIconSvg } from "../helpers/icon"
import { OpenView } from "../state/pagePanelState"
import { usePanelStateFromStateDispatchContext } from "../hooks/usePanelState"
import inTestGroup, { TestGroups } from "../userInTestGroup"
import { NotificationType } from "../realtime"

const NotificationBellIcon = ({
extraClasses,
Expand All @@ -14,8 +16,14 @@ const NotificationBellIcon = ({
currentView: { openView },
} = usePanelStateFromStateDispatchContext()
const { notifications } = useContext(NotificationsContext)

const inDetoursList = inTestGroup(TestGroups.DetoursList)
const unreadNotifications = (notifications || []).filter(
(notification) => notification.state === "unread"
(notification) =>
notification.state === "unread" &&
!(
notification.content.$type === NotificationType.Detour && !inDetoursList
)
)
const unreadBadge: boolean = unreadNotifications.length > 0

Expand Down
39 changes: 36 additions & 3 deletions assets/src/components/notificationCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactElement } from "react"
import React, { ReactNode } from "react"
import { useRoute, useRoutes } from "../contexts/routesContext"
import {
BlockWaiverNotification,
Expand All @@ -11,6 +11,8 @@ import { Route } from "../schedule"
import { formattedTime } from "../util/dateTime"
import { CardBody, CardProperties, CardReadable } from "./card"
import { fullStoryEvent } from "../helpers/fullStory"
import { RoutePill } from "./routePill"
import inTestGroup, { TestGroups } from "../userInTestGroup"

export const NotificationCard = ({
notification,
Expand All @@ -24,7 +26,7 @@ export const NotificationCard = ({
openVPPForCurrentVehicle: (notification: Notification) => void
hideLatestNotification?: () => void
noFocusOrHover?: boolean
}): ReactElement<HTMLElement> => {
}) => {
const routes = useRoutes(
isBlockWaiverNotification(notification) ? notification.content.routeIds : []
)
Expand All @@ -33,6 +35,14 @@ export const NotificationCard = ({
? notification.content.routeIdAtCreation
: null
)

if (
notification.content.$type === NotificationType.Detour &&
!inTestGroup(TestGroups.DetoursList)
) {
return null
}

const isUnread = notification.state === "unread"
return (
<CardReadable
Expand Down Expand Up @@ -87,6 +97,11 @@ export const title = (notification: Notification) => {
case NotificationType.BlockWaiver: {
return blockWaiverNotificationTitle(notification.content.reason)
}

case NotificationType.Detour: {
return "Detour - Active"
}

case NotificationType.BridgeMovement: {
switch (notification.content.status) {
case "lowered":
Expand Down Expand Up @@ -156,7 +171,7 @@ const description = (
notification: Notification,
routes: Route[],
routeAtCreation: Route | null
): string => {
): ReactNode => {
switch (notification.content.$type) {
case NotificationType.BlockWaiver: {
return blockWaiverDescription(
Expand All @@ -165,6 +180,24 @@ const description = (
routeAtCreation
)
}

case NotificationType.Detour: {
return (
<>
<div className="d-flex flex-row gap-2">
<RoutePill routeName={notification.content.route} />
<div>
firestack marked this conversation as resolved.
Show resolved Hide resolved
<div className="fw-semibold">{notification.content.headsign}</div>
<div className="fw-normal text-body-secondary">
From {notification.content.origin.split(" - ")[0]}
</div>
<div className="fw-normal">{notification.content.direction}</div>
</div>
</div>
</>
)
}

case NotificationType.BridgeMovement: {
switch (notification.content.status) {
case "raised":
Expand Down
6 changes: 4 additions & 2 deletions assets/src/models/detoursList.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { array, Infer, nullable, number, string, type } from "superstruct"

export type DetourId = number
hannahpurcell marked this conversation as resolved.
Show resolved Hide resolved
export interface SimpleDetour {
id: number
id: DetourId
route: string
direction: string
name: string
intersection: string
updatedAt: number
}

export const detourId = number()
export const SimpleDetourData = type({
id: number(),
id: detourId,
route: string(),
direction: string(),
name: string(),
Expand Down
30 changes: 29 additions & 1 deletion assets/src/models/notificationData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
NotificationContentTypes,
NotificationType,
} from "../realtime"
import { detourId } from "./detoursList"

const dateFromSeconds = coerce(
date(),
Expand Down Expand Up @@ -65,12 +66,27 @@ export const BridgeNotificationData = union([
}),
])

export const DetourNotificationData = type({
__struct__: literal(NotificationType.Detour),
detour_id: detourId,
headsign: string(),
route: string(),
direction: string(),
origin: string(),
})
firestack marked this conversation as resolved.
Show resolved Hide resolved

export type DetourNotificationData = Infer<typeof DetourNotificationData>

export const NotificationData = type({
id: coerce(string(), number(), (i) => i.toString()),
created_at: dateFromSeconds,
state: enums(["unread", "read", "deleted"]),
// Requires a field named `__struct__` to be present as the discriminator
content: union([BridgeNotificationData, BlockWaiverNotificationData]),
content: union([
BridgeNotificationData,
BlockWaiverNotificationData,
DetourNotificationData,
]),
})
export type NotificationData = Infer<typeof NotificationData>

Expand Down Expand Up @@ -116,6 +132,18 @@ export const notificationFromData = (
}
break
}

case NotificationType.Detour: {
content = {
$type: NotificationType.Detour,
detourId: notificationData.content.detour_id,
direction: notificationData.content.direction,
headsign: notificationData.content.headsign,
origin: notificationData.content.origin,
route: notificationData.content.route,
}
break
}
}

return {
Expand Down
13 changes: 13 additions & 0 deletions assets/src/realtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "./schedule"

import { Crowding } from "./models/crowding"
import { DetourId } from "./models/detoursList"

export interface BlockWaiver {
startTime: Date
Expand Down Expand Up @@ -59,6 +60,7 @@ export type NotificationId = string
export enum NotificationType {
BridgeMovement = "Elixir.Notifications.Db.BridgeMovement",
BlockWaiver = "Elixir.Notifications.Db.BlockWaiver",
Detour = "Elixir.Notifications.Db.Detour",
}

export interface BridgeLoweredNotification {
Expand Down Expand Up @@ -90,9 +92,20 @@ export type BlockWaiverNotification = {
endTime: Date | null
}

export type DetourNotification = {
$type: NotificationType.Detour
detourId: DetourId
headsign: string
route: string
direction: string
origin: string
}

export type NotificationContentTypes =
| BridgeNotification
| BlockWaiverNotification
| DetourNotification

export interface Notification<
TNotification extends NotificationContentTypes = NotificationContentTypes
> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,27 @@ exports[`NotificationBellIcon renders when the drawer is open and there are not
}
/>
`;

exports[`NotificationBellIcon renders when there are new detour notifications and user is not part of DetoursList 1`] = `
<body>
<div>
<span
class="c-notification-bell-icon c-notification-bell-icon--closed c-notification-bell-icon--read"
>
<svg />
</span>
</div>
</body>
`;

exports[`NotificationBellIcon renders when there are new detour notifications and user is part of DetoursList group 1`] = `
<body>
<div>
<span
class="c-notification-bell-icon c-notification-bell-icon--closed c-notification-bell-icon--unread"
>
<svg />
</span>
</div>
</body>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NotificationCard renders detour notification if user is in DetoursList group 1`] = `
<body>
<div>
<div
aria-labelledby="card-label-:rh:"
class="c-card c-card--kiwi"
>
<button
class="c-card__left"
>
<div
class="c-card__left-content"
>
<div
class="c-card__top-row"
>
<div
class="c-card__title"
id="card-label-:rh:"
>
<span>
<svg />
</span>
Detour - Active
</div>
<div
class="c-card__time"
>
0 min
</div>
</div>
<div
class="c-card__contents"
>
<div
class="c-card__body"
>
<div
class="d-flex flex-row gap-2"
>
<div
class="c-route-pill c-route-pill--bus"
>
3
</div>
<div>
<div
class="fw-semibold"
>
Headsign 3
</div>
<div
class="fw-normal text-body-secondary"
>
From
Origin station 3
</div>
<div
class="fw-normal"
>
Outbound
</div>
</div>
</div>
</div>
</div>
</div>
</button>
</div>
</div>
</body>
`;
Loading
Loading