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}
>
);
}
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 (
);
}
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}
);
}