import { isToday, isTomorrow } from "date-fns"; import { Bookmark, BookmarkCheck } from "lucide-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useFetcher } from "react-router"; import type { SidebarStream } from "~/features/core/streams/streams.server"; import type { LanguageCode } from "~/modules/i18n/config"; import { databaseTimestampToDate, formatDistanceToNow } from "~/utils/dates"; import { navIconUrl, tournamentRegisterPage } from "~/utils/urls"; import { Image } from "./Image"; import { ListLink } from "./SideNav"; import styles from "./StreamListItems.module.css"; import { TierPill } from "./TierPill"; export function StreamListItems({ streams, onClick, isLoggedIn, savedTournamentIds, }: { streams: SidebarStream[]; onClick?: () => void; isLoggedIn?: boolean; savedTournamentIds?: number[]; }) { const { t, i18n } = useTranslation(["front"]); const formatRelativeDate = (timestamp: number) => { const date = new Date(timestamp * 1000); const timeStr = date.toLocaleTimeString(i18n.language, { hour: "numeric", minute: "2-digit", }); if (isToday(date)) { const rtf = new Intl.RelativeTimeFormat(i18n.language, { numeric: "auto", }); const dayStr = rtf.format(0, "day"); return `${dayStr.charAt(0).toUpperCase() + dayStr.slice(1)}, ${timeStr}`; } if (isTomorrow(date)) { const rtf = new Intl.RelativeTimeFormat(i18n.language, { numeric: "auto", }); const dayStr = rtf.format(1, "day"); return `${dayStr.charAt(0).toUpperCase() + dayStr.slice(1)}, ${timeStr}`; } return date.toLocaleDateString(i18n.language, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }); }; return ( <> {streams.map((stream, i) => { const startsAtDate = databaseTimestampToDate(stream.startsAt); const isUpcoming = startsAtDate.getTime() > Date.now(); const prevStream = streams.at(i - 1); const prevIsLive = prevStream && databaseTimestampToDate(prevStream.startsAt).getTime() <= Date.now(); const showUpcomingDivider = isUpcoming && prevIsLive; const tournamentId = stream.id.startsWith("upcoming-") ? Number(stream.id.replace("upcoming-", "")) : null; return ( {showUpcomingDivider ? (
{t("front:sideNav.streams.upcoming")}
) : null} {stream.peakXp} ) : stream.subtitle ? ( stream.subtitle ) : isUpcoming ? ( formatRelativeDate(stream.startsAt) ) : ( formatDistanceToNow(startsAtDate, { addSuffix: true, language: i18n.language as LanguageCode, }) ) } badge={ !isUpcoming ? ( "LIVE" ) : (
{isLoggedIn && tournamentId !== null ? ( ) : null} {streamTierBadge(stream)}
) } onClick={onClick} > {stream.name}
); })} ); } function SaveTournamentStreamButton({ tournamentId, isSaved, }: { tournamentId: number; isSaved: boolean; }) { const fetcher = useFetcher(); const optimisticSaved = fetcher.formData?.get("_action") === "SAVE_TOURNAMENT" ? true : fetcher.formData?.get("_action") === "UNSAVE_TOURNAMENT" ? false : isSaved; const Icon = optimisticSaved ? BookmarkCheck : Bookmark; return ( e.stopPropagation()} > ); } function streamTierBadge(stream: SidebarStream): React.ReactNode { const tier = stream.tier ?? stream.tentativeTier; if (!tier) return undefined; return (
); }