- activities indexes + client_events/client_errors/rollups/alerts collections - global function middleware via createStart; reads skipped on success - actor stash, denial dedup, control-flow error guards - api route logging, auth event overrides, beacon endpoint, web vitals - centralized admin role checks, shared skeletons, award-badges fix - extracted deriveRunState with tests
170 lines
5.9 KiB
TypeScript
170 lines
5.9 KiB
TypeScript
import { Suspense, useMemo } from "react";
|
|
import { Trans, useLingui } from "@lingui/react/macro";
|
|
import { Tournament } from "../../types";
|
|
import { useAuth } from "@/contexts/auth-context";
|
|
import { useIsAdmin } from "@/features/core/hooks/use-is-admin";
|
|
import { Box, Divider, Paper, SimpleGrid, Stack, Text } from "@mantine/core";
|
|
import Countdown from "@/components/countdown";
|
|
import { TreeStructureIcon, UsersIcon } from "@phosphor-icons/react";
|
|
import { useNavigate } from "@tanstack/react-router";
|
|
import EnrollTeam from "./enroll-team";
|
|
import EnrollFreeAgent from "./enroll-free-agent";
|
|
import TeamListButton from "./team-list-button";
|
|
import EnrolledPlayersListButton from "./enrolled-players-list-button";
|
|
import Header from "../started-tournament/header";
|
|
import SectionHeading from "../started-tournament/sections/section-heading";
|
|
import { NavTile } from "../started-tournament/sections/nav-grid";
|
|
import TeamCardSkeleton from "@/features/teams/components/team-card-skeleton";
|
|
import TeamCard from "@/features/teams/components/team-card";
|
|
import RegionalTeamCard from "@/features/teams/components/regional-team-card";
|
|
import UpdateTeam from "./update-team";
|
|
import UnenrollTeam from "./unenroll-team";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { tournamentKeys, useFreeAgents } from "../../queries";
|
|
import RulesListButton from "./rules-list-button";
|
|
import EnrolledFreeAgent from "./enrolled-free-agent";
|
|
|
|
const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
|
tournament,
|
|
}) => {
|
|
const { user } = useAuth();
|
|
const navigate = useNavigate();
|
|
const { t } = useLingui();
|
|
|
|
const isAdmin = useIsAdmin();
|
|
const userTeam = useMemo(
|
|
() =>
|
|
tournament.teams?.find((team) =>
|
|
team.players?.some((player) => player.id === user?.id)
|
|
),
|
|
[tournament.teams, user?.id]
|
|
);
|
|
const isUserEnrolled = !!userTeam;
|
|
|
|
const enrollmentDeadline = tournament.enroll_time
|
|
? new Date(tournament.enroll_time)
|
|
: new Date(tournament.start_time);
|
|
const isEnrollmentOpen = enrollmentDeadline > new Date();
|
|
|
|
const queryClient = useQueryClient();
|
|
const handleSubmit = () => {
|
|
queryClient.invalidateQueries({ queryKey: tournamentKeys.current });
|
|
};
|
|
|
|
const { data: free_agents } = useFreeAgents(tournament.id);
|
|
const isFreeAgent = useMemo(() => !isUserEnrolled && free_agents.find(a => a.player?.id === user?.id), [free_agents, isUserEnrolled]);
|
|
|
|
return (
|
|
<Stack gap="lg">
|
|
<Header tournament={tournament} />
|
|
|
|
{tournament.desc && (
|
|
<Text px="md" size="sm" c="dimmed" style={{ whiteSpace: "pre-wrap" }}>
|
|
{tournament.desc}
|
|
</Text>
|
|
)}
|
|
|
|
<Box>
|
|
<SectionHeading
|
|
label={t`Enrollment`}
|
|
action={
|
|
isEnrollmentOpen ? (
|
|
<Countdown date={enrollmentDeadline} />
|
|
) : undefined
|
|
}
|
|
/>
|
|
<Box px="md">
|
|
<Paper withBorder p="md">
|
|
<Stack gap="sm">
|
|
{!isUserEnrolled && !isEnrollmentOpen && (
|
|
<Text fw={600} c="dimmed" size="sm">
|
|
<Trans>Enrollment has been closed for this tournament.</Trans>
|
|
</Text>
|
|
)}
|
|
|
|
{!isUserEnrolled && isEnrollmentOpen && !isFreeAgent && (
|
|
<>
|
|
{!tournament.regional && (
|
|
<>
|
|
<EnrollTeam
|
|
tournamentId={tournament.id}
|
|
onSubmit={handleSubmit}
|
|
/>
|
|
<Divider my={0} label={t`or`} />
|
|
</>
|
|
)}
|
|
<EnrollFreeAgent
|
|
tournamentId={tournament.id}
|
|
isRegional={tournament.regional}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{isUserEnrolled && (
|
|
<>
|
|
<Suspense fallback={<TeamCardSkeleton />}>
|
|
{tournament.regional === true ? (
|
|
<RegionalTeamCard teamId={userTeam.id} />
|
|
) : (
|
|
<TeamCard teamId={userTeam.id} />
|
|
)}
|
|
</Suspense>
|
|
{tournament.regional !== true && (
|
|
<>
|
|
<UpdateTeam tournamentId={tournament.id} teamId={userTeam.id} />
|
|
{isEnrollmentOpen && (
|
|
<UnenrollTeam
|
|
tournamentId={tournament.id}
|
|
teamId={userTeam.id}
|
|
onSubmit={handleSubmit}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{isFreeAgent && isEnrollmentOpen && (
|
|
<EnrolledFreeAgent
|
|
tournamentId={tournament.id}
|
|
isRegional={tournament.regional}
|
|
/>
|
|
)}
|
|
</Stack>
|
|
</Paper>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Box>
|
|
<Divider mb="sm" />
|
|
<Box px="md">
|
|
<SimpleGrid cols={3} spacing="sm" verticalSpacing="sm">
|
|
{isAdmin && (
|
|
<NavTile
|
|
label={t`Manage`}
|
|
Icon={UsersIcon}
|
|
onClick={() =>
|
|
navigate({ to: `/admin/tournaments/${tournament.id}` })
|
|
}
|
|
/>
|
|
)}
|
|
<NavTile label={t`Bracket`} Icon={TreeStructureIcon} disabled />
|
|
{tournament.regional === true ? (
|
|
(tournament.teams && tournament.teams.length > 0) ? (
|
|
<TeamListButton teams={tournament.teams} isRegional={true} />
|
|
) : (
|
|
<EnrolledPlayersListButton tournamentId={tournament.id} />
|
|
)
|
|
) : (
|
|
<TeamListButton teams={tournament.teams || []} isRegional={false} />
|
|
)}
|
|
<RulesListButton tournamentId={tournament.id} />
|
|
</SimpleGrid>
|
|
</Box>
|
|
</Box>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export default UpcomingTournament;
|