mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 09:20:24 -05:00
* Initial * CSS lint * Test CI * Add 1v1, 2v2, and 3v3 Tags (#1771) * Initial * CSS lint * Test CI * Rename step --------- Co-authored-by: xi <104683822+ximk@users.noreply.github.com>
137 lines
3.8 KiB
TypeScript
137 lines
3.8 KiB
TypeScript
import { Link, useLoaderData } from "@remix-run/react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Avatar } from "~/components/Avatar";
|
|
import { TierImage, WeaponImage } from "~/components/Image";
|
|
import { Main } from "~/components/Main";
|
|
import { UserIcon } from "~/components/icons/User";
|
|
import { useAutoRerender } from "~/hooks/useAutoRerender";
|
|
import { useIsMounted } from "~/hooks/useIsMounted";
|
|
import { twitchThumbnailUrlToSrc } from "~/modules/twitch/utils";
|
|
import { databaseTimestampToDate } from "~/utils/dates";
|
|
import type { SendouRouteHandle } from "~/utils/remix";
|
|
import { FAQ_PAGE, sendouQMatchPage, twitchUrl, userPage } from "~/utils/urls";
|
|
import { cachedStreams } from "../core/streams.server";
|
|
|
|
import "~/features/sendouq/q.css";
|
|
|
|
export const handle: SendouRouteHandle = {
|
|
i18n: ["q"],
|
|
};
|
|
|
|
export const loader = async () => {
|
|
return {
|
|
streams: await cachedStreams(),
|
|
};
|
|
};
|
|
|
|
export default function SendouQStreamsPage() {
|
|
const { t } = useTranslation(["q"]);
|
|
const data = useLoaderData<typeof loader>();
|
|
|
|
const ownStreamNote = (
|
|
<div className="text-xs mt-4 font-body">
|
|
{t("q:streams.ownStreamInfo")}{" "}
|
|
<Link to={FAQ_PAGE}>{t("q:streams.ownStreamInfo.linkText")}</Link>
|
|
</div>
|
|
);
|
|
|
|
if (data.streams.length === 0) {
|
|
return (
|
|
<Main className="text-lighter text-lg font-bold text-center">
|
|
{t("q:streams.noStreams")}
|
|
{ownStreamNote}
|
|
</Main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Main>
|
|
<div className="stack horizontal lg flex-wrap justify-center">
|
|
{data.streams.map((streamedMatch) => {
|
|
return (
|
|
<div key={streamedMatch.user.id} className="stack sm">
|
|
<div className="stack horizontal justify-between items-end">
|
|
<Link
|
|
to={userPage(streamedMatch.user)}
|
|
className="q-stream__stream__user-container"
|
|
>
|
|
<Avatar size="xxs" user={streamedMatch.user} />{" "}
|
|
{streamedMatch.user.username}
|
|
</Link>
|
|
<div className="stack horizontal sm">
|
|
{streamedMatch.weaponSplId ? (
|
|
<div className="q-stream__info-circle">
|
|
<WeaponImage
|
|
weaponSplId={streamedMatch.weaponSplId}
|
|
size={24}
|
|
variant="build"
|
|
/>
|
|
</div>
|
|
) : null}
|
|
{streamedMatch.tier ? (
|
|
<div className="q-stream__info-circle">
|
|
<TierImage tier={streamedMatch.tier} width={24} />
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<a
|
|
href={twitchUrl(streamedMatch.user.twitch)}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
<img
|
|
alt=""
|
|
src={twitchThumbnailUrlToSrc(
|
|
streamedMatch.stream.thumbnailUrl,
|
|
)}
|
|
width={320}
|
|
height={180}
|
|
/>
|
|
</a>
|
|
<div className="stack horizontal justify-between">
|
|
<div className="text-sm stack horizontal sm">
|
|
<div>
|
|
<Link to={sendouQMatchPage(streamedMatch.match.id)}>
|
|
#{streamedMatch.match.id}
|
|
</Link>
|
|
</div>
|
|
<RelativeStartTime
|
|
startedAt={databaseTimestampToDate(
|
|
streamedMatch.match.createdAt,
|
|
)}
|
|
/>
|
|
</div>
|
|
<div className="q-stream__stream__viewer-count">
|
|
<UserIcon />
|
|
{streamedMatch.stream.viewerCount}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
{ownStreamNote}
|
|
</Main>
|
|
);
|
|
}
|
|
|
|
function RelativeStartTime({ startedAt }: { startedAt: Date }) {
|
|
const { i18n } = useTranslation();
|
|
const isMounted = useIsMounted();
|
|
useAutoRerender();
|
|
|
|
if (!isMounted) return null;
|
|
|
|
const minutesAgo = Math.floor((startedAt.getTime() - Date.now()) / 1000 / 60);
|
|
const formatter = new Intl.RelativeTimeFormat(i18n.language, {
|
|
style: "short",
|
|
});
|
|
|
|
return (
|
|
<span className="text-lighter">
|
|
{formatter.format(minutesAgo, "minute")}
|
|
</span>
|
|
);
|
|
}
|