import { Link, useLoaderData } from "@remix-run/react"; import clsx from "clsx"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { Avatar } from "~/components/Avatar"; import { Button } from "~/components/Button"; import { Divider } from "~/components/Divider"; import { Flag } from "~/components/Flag"; import { Image } from "~/components/Image"; import { Main } from "~/components/Main"; import { NewTabs } from "~/components/NewTabs"; import { ArrowRightIcon } from "~/components/icons/ArrowRight"; import { BSKYLikeIcon } from "~/components/icons/BSKYLike"; import { BSKYReplyIcon } from "~/components/icons/BSKYReply"; import { BSKYRepostIcon } from "~/components/icons/BSKYRepost"; import { ExternalIcon } from "~/components/icons/External"; import { KeyIcon } from "~/components/icons/Key"; import { LogOutIcon } from "~/components/icons/LogOut"; import { SearchIcon } from "~/components/icons/Search"; import { UsersIcon } from "~/components/icons/Users"; import { navItems } from "~/components/layout/nav-items"; import { useUser } from "~/features/auth/core/user"; import type * as Changelog from "~/features/front-page/core/Changelog.server"; import { currentOrPreviousSeason, nextSeason, previousSeason, } from "~/features/mmr/season"; import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils"; import { useIsMounted } from "~/hooks/useIsMounted"; import { databaseTimestampToDate } from "~/utils/dates"; import type { SendouRouteHandle } from "~/utils/remix.server"; import { BLANK_IMAGE_URL, CALENDAR_TOURNAMENTS_PAGE, LOG_OUT_URL, LUTI_PAGE, SENDOUQ_PAGE, leaderboardsPage, navIconUrl, sqHeaderGuyImageUrl, tournamentPage, userSubmittedImage, } from "~/utils/urls"; import type * as ShowcaseTournaments from "../core/ShowcaseTournaments.server"; import { type LeaderboardEntry, loader } from "../loaders/index.server"; export { loader }; import "~/styles/front.css"; export const handle: SendouRouteHandle = { i18n: ["front"], }; export default function FrontPage() { return (
); } function DesktopSideNav() { const user = useUser(); const { t } = useTranslation(["common"]); return ( ); } function SeasonBanner() { const { t, i18n } = useTranslation(["front"]); const season = nextSeason(new Date()) ?? currentOrPreviousSeason(new Date())!; const _previousSeason = previousSeason(new Date()); const isMounted = useIsMounted(); const isInFuture = new Date() < season.starts; const isShowingPreviousSeason = _previousSeason?.nth === season.nth; if (isShowingPreviousSeason) return null; return (
{t("front:sq.season", { nth: season.nth })}
{isMounted ? (
{season.starts.toLocaleDateString(i18n.language, { month: "long", day: "numeric", })}{" "} -{" "} {season.ends.toLocaleDateString(i18n.language, { month: "long", day: "numeric", })}
) : (
X
)}
{isInFuture ? ( <>{t("front:sq.prepare")} ) : ( <>{t("front:sq.participate")} )}
); } function LeagueBanner() { const showBannerFor = import.meta.env.VITE_SHOW_BANNER_FOR_SEASON; if (!showBannerFor) return null; return ( Registration now open for Leagues Under The Ink (LUTI) Season{" "} {showBannerFor}! ); } function TournamentCards() { const { t } = useTranslation(["front"]); const data = useLoaderData(); if ( data.tournaments.participatingFor.length === 0 && data.tournaments.organizingFor.length === 0 && data.tournaments.showcase.length === 0 ) { return null; } return (
, }, { label: t("front:showcase.tabs.organizer"), hidden: data.tournaments.organizingFor.length === 0, icon: , }, { label: t("front:showcase.tabs.discover"), hidden: data.tournaments.showcase.length === 0, icon: , }, ]} content={[ { key: "your", hidden: data.tournaments.participatingFor.length === 0, element: ( ), }, { key: "organizer", hidden: data.tournaments.organizingFor.length === 0, element: ( ), }, { key: "discover", hidden: data.tournaments.showcase.length === 0, element: ( ), }, ]} />
); } function ShowcaseTournamentScroller({ tournaments, }: { tournaments: ShowcaseTournaments.ShowcaseTournament[] }) { return (
{tournaments.map((tournament) => ( ))}
); } function AllTournamentsLinkCard() { const { t } = useTranslation(["front"]); return ( {t("front:showcase.viewAll")} ); } function TournamentCard({ tournament, topSpaced, }: { tournament: ShowcaseTournaments.ShowcaseTournament; topSpaced?: boolean; }) { const isMounted = useIsMounted(); const { t, i18n } = useTranslation(["front", "common"]); const time = () => { if (!isMounted) return "Placeholder"; const date = databaseTimestampToDate(tournament.startTime); return date.toLocaleString(i18n.language, { month: "short", day: "numeric", hour: "numeric", weekday: "short", minute: date.getMinutes() !== 0 ? "numeric" : undefined, }); }; return (
{tournament.organization ? (
{tournament.organization.name}
) : null}
{tournament.name}{" "}
{tournament.firstPlacer ? ( ) : null}
{tournament.teamsCount}
{tournament.isRanked ? (
{t("front:showcase.card.ranked")}
) : (
{t("front:showcase.card.unranked")}
)}
); } function TournamentFirstPlacers({ firstPlacer, }: { firstPlacer: NonNullable< ShowcaseTournaments.ShowcaseTournament["firstPlacer"] >; }) { const { t } = useTranslation(["front"]); return (
{firstPlacer.logoUrl ? ( ) : null}{" "}
{firstPlacer.teamName}
{t("front:showcase.card.winner")}
{firstPlacer.members.map((member) => (
{member.country ? : null} {member.username}{" "}
))} {firstPlacer.notShownMembersCount > 0 ? (
+{firstPlacer.notShownMembersCount}
) : null}
); } function ResultHighlights() { const { t } = useTranslation(["front"]); const data = useLoaderData(); // should not happen if ( !data.leaderboards.team.length || !data.leaderboards.user.length || !data.tournaments.results.length ) { return null; } const season = currentOrPreviousSeason(new Date())!; const recentResults = ( <>

{t("front:showcase.results")}

{data.tournaments.results.map((tournament) => ( ))}
); return ( <>

{t("front:leaderboards.topPlayers")}

{t("front:leaderboards.topTeams")}

{recentResults}
{recentResults}
); } function Leaderboard({ entries, fullLeaderboardUrl, }: { entries: LeaderboardEntry[]; fullLeaderboardUrl: string }) { const { t } = useTranslation(["front"]); return (
{entries.map((entry, index) => (
{index + 1}
{entry.name}
{entry.power.toFixed(2)}
))}
{t("front:leaderboards.viewFull")}
); } function ChangelogList() { const { t } = useTranslation(["front"]); const data = useLoaderData(); if (data.changelog.length === 0) return null; return (
{t("front:updates.header")} {data.changelog.map((item) => (
))} {t("front:updates.viewPast")}{" "}
); } const ADMIN_PFP_URL = "https://cdn.discordapp.com/avatars/79237403620945920/6fc41a44b069a0d2152ac06d1e496c6c.webp?size=80"; function ChangelogItem({ item, }: { item: Changelog.ChangelogItem; }) { return (
Sendou{" "} {item.createdAtRelative}
{item.text} {item.images.length > 0 ? (
{item.images.map((image) => ( ))}
) : null}
); } function BSKYIconLink({ children, count, postUrl, }: { children: React.ReactNode; count: number; postUrl: string }) { return ( {children} {count} ); }