diff --git a/app/features/notifications/components/NotificationList.tsx b/app/features/notifications/components/NotificationList.tsx index 3d2b29d99..2ec373477 100644 --- a/app/features/notifications/components/NotificationList.tsx +++ b/app/features/notifications/components/NotificationList.tsx @@ -4,7 +4,6 @@ import { Link } from "react-router"; import { Image } from "~/components/Image"; import type { LoaderNotification } from "~/components/layout/NotificationPopover"; import { - mapMetaForTranslation, notificationLink, notificationNavIcon, } from "~/features/notifications/notifications-utils"; @@ -23,7 +22,7 @@ export function NotificationItem({ notification: LoaderNotification; onClose?: () => void; }) { - const { t, i18n } = useTranslation(["common"]); + const { t } = useTranslation(["common"]); return ( : null}
- {t( - `common:notifications.text.${notification.type}`, - mapMetaForTranslation(notification, i18n.language), - )} + {t(`common:notifications.text.${notification.type}`, notification.meta)}
{formatDistance( diff --git a/app/features/notifications/core/notify.server.test.ts b/app/features/notifications/core/notify.server.test.ts index 3cb31ac2a..3e0126f52 100644 --- a/app/features/notifications/core/notify.server.test.ts +++ b/app/features/notifications/core/notify.server.test.ts @@ -139,7 +139,7 @@ describe("notify()", () => { userIds: [10, 11], notification: { type: "SCRIM_SCHEDULED", - meta: { id: 1, at: 123 }, + meta: { id: 1, opponentTeamName: "Alpha" }, }, }); @@ -147,7 +147,7 @@ describe("notify()", () => { userIds: [10, 11], notification: { type: "SCRIM_CANCELED", - meta: { id: 1, at: 123 }, + meta: { id: 1, opponentTeamName: "Alpha" }, }, }); @@ -346,7 +346,7 @@ describe("notify() - web push notifications", () => { expect(mockSendNotification).not.toHaveBeenCalled(); }); - test("formats timestamp for scrim notifications", async () => { + test("includes opponent team name for scrim notifications", async () => { const mockSubscription = { endpoint: "https://fcm.googleapis.com/fcm/send/test", keys: { @@ -367,13 +367,11 @@ describe("notify() - web push notifications", () => { mockWebPushEnabled.value = true; - const testTimestamp = new Date("2024-01-15T15:30:00Z").getTime(); - await notify({ userIds: [1], notification: { type: "SCRIM_SCHEDULED", - meta: { id: 1, at: testTimestamp }, + meta: { id: 1, opponentTeamName: "Sendou's pickup" }, }, }); @@ -383,8 +381,6 @@ describe("notify() - web push notifications", () => { const payload = JSON.parse(callArgs); expect(payload.title).toBe("Scrim Scheduled"); - expect(payload.body).toMatch( - /New scrim scheduled at \d+\/\d+, \d+:\d+ (AM|PM)/, - ); + expect(payload.body).toBe("New scrim scheduled vs. Sendou's pickup"); }); }); diff --git a/app/features/notifications/core/notify.server.ts b/app/features/notifications/core/notify.server.ts index 1f97022d7..b1e7b13e5 100644 --- a/app/features/notifications/core/notify.server.ts +++ b/app/features/notifications/core/notify.server.ts @@ -7,10 +7,7 @@ import { i18next } from "../../../modules/i18n/i18next.server"; import { logger } from "../../../utils/logger"; import * as NotificationRepository from "../NotificationRepository.server"; import type { Notification } from "../notifications-types"; -import { - mapMetaForTranslation, - notificationLink, -} from "../notifications-utils"; +import { notificationLink } from "../notifications-utils"; import webPush, { webPushEnabled } from "./webPush.server"; const NOTIFICATION_URGENCY: Record = { @@ -168,10 +165,12 @@ function pushNotificationOptions( ): Parameters[1] & { title: string; } { - const meta = mapMetaForTranslation(notification, "en-US"); return { title: t(`common:notifications.title.${notification.type}`), - body: t(`common:notifications.text.${notification.type}`, meta), + body: t( + `common:notifications.text.${notification.type}`, + notification.meta, + ), icon: notification.pictureUrl ?? "/static-assets/img/app-icon.png", data: { url: notificationLink(notification) }, }; diff --git a/app/features/notifications/notifications-types.ts b/app/features/notifications/notifications-types.ts index 4519d9e1e..f213d2a1e 100644 --- a/app/features/notifications/notifications-types.ts +++ b/app/features/notifications/notifications-types.ts @@ -62,9 +62,15 @@ export type Notification = > | NotificationItem<"SEASON_STARTED", { seasonNth: number }> | NotificationItem<"SCRIM_NEW_REQUEST", { fromUsername: string }> - | NotificationItem<"SCRIM_SCHEDULED", { id: number; at: number }> - | NotificationItem<"SCRIM_CANCELED", { id: number; at: number }> - | NotificationItem<"SCRIM_STARTING_SOON", { id: number; at: number }> + | NotificationItem< + "SCRIM_SCHEDULED", + { id: number; opponentTeamName: string } + > + | NotificationItem<"SCRIM_CANCELED", { id: number; opponentTeamName: string }> + | NotificationItem< + "SCRIM_STARTING_SOON", + { id: number; opponentTeamName: string } + > | NotificationItem<"COMMISSIONS_CLOSED", { discordId: string }> | NotificationItem<"FRIEND_REQUEST_RECEIVED", { senderUsername: string }> | NotificationItem< diff --git a/app/features/notifications/notifications-utils.ts b/app/features/notifications/notifications-utils.ts index be53272be..642cb87a6 100644 --- a/app/features/notifications/notifications-utils.ts +++ b/app/features/notifications/notifications-utils.ts @@ -107,27 +107,3 @@ export const notificationLink = (notification: Notification) => { assertUnreachable(notification); } }; - -/** Takes the `meta` object of a notification and transforms it (if needed) to show the translated string to user */ -export const mapMetaForTranslation = ( - notification: Notification, - language: string, -) => { - if ( - notification.type === "SCRIM_SCHEDULED" || - notification.type === "SCRIM_CANCELED" || - notification.type === "SCRIM_STARTING_SOON" - ) { - return { - ...notification.meta, - timeString: new Date(notification.meta.at).toLocaleString(language, { - day: "numeric", - month: "numeric", - hour: "numeric", - minute: "numeric", - }), - }; - } - - return notification.meta; -}; diff --git a/app/features/scrims/actions/scrims.$id.server.ts b/app/features/scrims/actions/scrims.$id.server.ts index aa45f25c7..d78e2c1c2 100644 --- a/app/features/scrims/actions/scrims.$id.server.ts +++ b/app/features/scrims/actions/scrims.$id.server.ts @@ -8,10 +8,7 @@ import { parseRequestPayload, } from "~/utils/remix.server"; import { idObject } from "~/utils/zod"; -import { - databaseTimestampToDate, - databaseTimestampToJavascriptTimestamp, -} from "../../../utils/dates"; +import { databaseTimestampToDate } from "../../../utils/dates"; import { errorToast } from "../../../utils/remix.server"; import { requireUser } from "../../auth/core/user.server"; import * as Scrim from "../core/Scrim"; @@ -42,17 +39,29 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { reason: data.reason, }); - notify({ - userIds: Scrim.participantIdsListFromAccepted(post), - defaultSeenUserIds: [user.id], - notification: { - type: "SCRIM_CANCELED", - meta: { - id: post.id, - at: databaseTimestampToJavascriptTimestamp(Scrim.getStartTime(post)), + const acceptedRequest = post.requests.find((r) => r.isAccepted); + if (acceptedRequest) { + const postTeamName = Scrim.sideDisplayName(post); + const requestTeamName = Scrim.sideDisplayName(acceptedRequest); + + notify({ + userIds: post.users.map((m) => m.id), + defaultSeenUserIds: [user.id], + notification: { + type: "SCRIM_CANCELED", + meta: { id: post.id, opponentTeamName: requestTeamName }, }, - }, - }); + }); + + notify({ + userIds: acceptedRequest.users.map((m) => m.id), + defaultSeenUserIds: [user.id], + notification: { + type: "SCRIM_CANCELED", + meta: { id: post.id, opponentTeamName: postTeamName }, + }, + }); + } return null; }; diff --git a/app/features/scrims/actions/scrims.server.ts b/app/features/scrims/actions/scrims.server.ts index 260bf369b..e1a29dff1 100644 --- a/app/features/scrims/actions/scrims.server.ts +++ b/app/features/scrims/actions/scrims.server.ts @@ -11,7 +11,6 @@ import * as UserRepository from "~/features/user-page/UserRepository.server"; import { requirePermission } from "~/modules/permissions/guards.server"; import { databaseTimestampToDate, - databaseTimestampToJavascriptTimestamp, dateToDatabaseTimestamp, } from "~/utils/dates"; import { ConcurrentModificationError } from "~/utils/errors"; @@ -157,18 +156,24 @@ export const action = async ({ request }: ActionFunctionArgs) => { }); } + const postTeamName = Scrim.sideDisplayName(post); + const requestTeamName = Scrim.sideDisplayName(request); + notify({ - userIds: [ - ...post.users.map((m) => m.id), - ...request.users.map((m) => m.id), - ], + userIds: post.users.map((m) => m.id), defaultSeenUserIds: [user.id], notification: { type: "SCRIM_SCHEDULED", - meta: { - id: post.id, - at: databaseTimestampToJavascriptTimestamp(request.at ?? post.at), - }, + meta: { id: post.id, opponentTeamName: requestTeamName }, + }, + }); + + notify({ + userIds: request.users.map((m) => m.id), + defaultSeenUserIds: [user.id], + notification: { + type: "SCRIM_SCHEDULED", + meta: { id: post.id, opponentTeamName: postTeamName }, }, }); diff --git a/app/features/scrims/core/Scrim.test.ts b/app/features/scrims/core/Scrim.test.ts index afdf51f9f..602a96b48 100644 --- a/app/features/scrims/core/Scrim.test.ts +++ b/app/features/scrims/core/Scrim.test.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from "vitest"; import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates"; import type { ScrimFilters, ScrimPost } from "../scrims-types"; -import { applyFilters, participantIdsListFromAccepted } from "./Scrim"; +import { + applyFilters, + participantIdsListFromAccepted, + sideDisplayName, +} from "./Scrim"; type MockUser = { id: number }; type MockRequest = { isAccepted: boolean; users: MockUser[] }; @@ -78,6 +82,27 @@ describe("participantIdsListFromAccepted", () => { }); }); +describe("sideDisplayName", () => { + it("returns the team name when team is set", () => { + const result = sideDisplayName({ + team: { name: "Team Olive" }, + users: [{ username: "sendou", isOwner: true }], + }); + expect(result).toBe("Team Olive"); + }); + + it("falls back to {owner}'s pickup when team is null", () => { + const result = sideDisplayName({ + team: null, + users: [ + { username: "alice", isOwner: false }, + { username: "sendou", isOwner: true }, + ], + }); + expect(result).toBe("sendou's pickup"); + }); +}); + describe("applyFilters", () => { function createPostForFilters( at: Date, diff --git a/app/features/scrims/core/Scrim.ts b/app/features/scrims/core/Scrim.ts index e5ca8f842..1a45a425d 100644 --- a/app/features/scrims/core/Scrim.ts +++ b/app/features/scrims/core/Scrim.ts @@ -50,6 +50,19 @@ export function getStartTime(post: ScrimPost): number { return acceptedRequest?.at ?? post.at; } +/** + * Returns a display name for a scrim side: the team name when set, + * otherwise "{ownerUsername}'s pickup". + */ +export function sideDisplayName(side: { + team: { name: string } | null; + users: Array<{ username: string; isOwner: boolean }>; +}): string { + if (side.team) return side.team.name; + const owner = side.users.find((u) => u.isOwner) ?? side.users[0]; + return `${owner.username}'s pickup`; +} + export function applyFilters(post: ScrimPost, filters: ScrimFilters): boolean { const hasMinFilter = filters.divs?.min !== null; const hasMaxFilter = filters.divs?.max !== null; diff --git a/app/routines/notifyScrimStartingSoon.ts b/app/routines/notifyScrimStartingSoon.ts index 436b490ec..8b72c3f25 100644 --- a/app/routines/notifyScrimStartingSoon.ts +++ b/app/routines/notifyScrimStartingSoon.ts @@ -2,7 +2,6 @@ import { add, sub } from "date-fns"; import { notify } from "../features/notifications/core/notify.server"; import * as Scrim from "../features/scrims/core/Scrim"; import * as ScrimPostRepository from "../features/scrims/ScrimPostRepository.server"; -import { databaseTimestampToJavascriptTimestamp } from "../utils/dates"; import { logger } from "../utils/logger"; import { Routine } from "./routine.server"; @@ -19,23 +18,30 @@ export const NotifyScrimStartingSoonRoutine = new Routine({ }); for (const scrim of scrims) { - const participantIds = Scrim.participantIdsListFromAccepted(scrim); + const acceptedRequest = scrim.requests.find((r) => r.isAccepted); + if (!acceptedRequest) continue; + + const postTeamName = Scrim.sideDisplayName(scrim); + const requestTeamName = Scrim.sideDisplayName(acceptedRequest); logger.info( - `Notifying scrim starting soon for scrim ${scrim.id} with ${participantIds.length} participants`, + `Notifying scrim starting soon for scrim ${scrim.id} with ${scrim.users.length + acceptedRequest.users.length} participants`, ); await notify({ notification: { type: "SCRIM_STARTING_SOON", - meta: { - id: scrim.id, - at: databaseTimestampToJavascriptTimestamp( - Scrim.getStartTime(scrim), - ), - }, + meta: { id: scrim.id, opponentTeamName: requestTeamName }, }, - userIds: participantIds, + userIds: scrim.users.map((u) => u.id), + }); + + await notify({ + notification: { + type: "SCRIM_STARTING_SOON", + meta: { id: scrim.id, opponentTeamName: postTeamName }, + }, + userIds: acceptedRequest.users.map((u) => u.id), }); } }, diff --git a/locales/en/common.json b/locales/en/common.json index aec67cec0..7cecf893b 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -79,11 +79,11 @@ "notifications.title.SCRIM_NEW_REQUEST": "New Scrim Request", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} requested a scrim", "notifications.title.SCRIM_SCHEDULED": "Scrim Scheduled", - "notifications.text.SCRIM_SCHEDULED": "New scrim scheduled at {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "New scrim scheduled vs. {{opponentTeamName}}", "notifications.title.SCRIM_CANCELED": "Scrim Canceled", - "notifications.text.SCRIM_CANCELED": "The scrim at {{timeString}} was canceled", + "notifications.text.SCRIM_CANCELED": "The scrim vs. {{opponentTeamName}} was canceled", "notifications.title.SCRIM_STARTING_SOON": "Scrim Starting Soon", - "notifications.text.SCRIM_STARTING_SOON": "Your scrim at {{timeString}} is starting soon", + "notifications.text.SCRIM_STARTING_SOON": "Your scrim vs. {{opponentTeamName}} is starting soon", "notifications.title.COMMISSIONS_CLOSED": "Commissions Closed", "notifications.text.COMMISSIONS_CLOSED": "If your commissions are still open, please re-enable them", "notifications.title.FRIEND_REQUEST_RECEIVED": "Friend Request", diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index 7963639bf..89999e731 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -79,11 +79,11 @@ "notifications.title.SCRIM_NEW_REQUEST": "Nueva Solicitud de Scrim", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} ha solicitado un scrim", "notifications.title.SCRIM_SCHEDULED": "Scrim Programado", - "notifications.text.SCRIM_SCHEDULED": "Nuevo scrim programado para las {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "Scrim Cancelado", - "notifications.text.SCRIM_CANCELED": "El scrim de las {{timeString}} fue cancelado", + "notifications.text.SCRIM_CANCELED": "", "notifications.title.SCRIM_STARTING_SOON": "El scrim empieza pronto", - "notifications.text.SCRIM_STARTING_SOON": "Tu scrim de las {{timeString}} empieza pronto", + "notifications.text.SCRIM_STARTING_SOON": "", "notifications.title.COMMISSIONS_CLOSED": "Comisiones Cerradas", "notifications.text.COMMISSIONS_CLOSED": "Si tus comisiones siguen abiertas, por favor vuelve a activarlas", "notifications.title.FRIEND_REQUEST_RECEIVED": "", diff --git a/locales/fr-EU/common.json b/locales/fr-EU/common.json index dc9fb372c..ff4ceef2c 100644 --- a/locales/fr-EU/common.json +++ b/locales/fr-EU/common.json @@ -79,7 +79,7 @@ "notifications.title.SCRIM_NEW_REQUEST": "Nouvelle Demande De Scrim", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} vous demande de scrim", "notifications.title.SCRIM_SCHEDULED": "Scrim Programmé", - "notifications.text.SCRIM_SCHEDULED": "Nouveau scrim programmé à {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", "notifications.title.SCRIM_STARTING_SOON": "", diff --git a/locales/ru/common.json b/locales/ru/common.json index 2f2d22a5a..77246069f 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -79,7 +79,7 @@ "notifications.title.SCRIM_NEW_REQUEST": "Новый Скрим Запрос", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} запросил скрим", "notifications.title.SCRIM_SCHEDULED": "Скрим Запланирован", - "notifications.text.SCRIM_SCHEDULED": "Новый скрим запланирован на {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", "notifications.title.SCRIM_STARTING_SOON": "",