sendou.ink/app/features/tournament-bracket/components/TournamentTeamActions.tsx
Kalle c3444349b0
Free bracket progression (#1959)
* Initial

* Start implementing TournamentFormatSelector

* Progress

* Small progress

* Add source

* Progress

* Skeleton

* Progress

* Rename progression

* Can submit progression

* Fix potential issue with caching errors

* Settings

* isFinals/isUnderground

* Valid formats tests

* New bracket check in progress

* Perf optimization: simulate brackets only frontend

* Admin check in fix

* resolvesWinner logic

* SAME_PLACEMENT_TO_MULTIPLE_BRACKETS

* Structure work

* Edit bracket while tournament in progress initial

* Delayed check in to follow up bracket

* Progress validation

* NEGATIVE_PROGRESSION

* test first sources = null

* Different text when invitational

* More checks

* Validate changed are in preview

* Rename

* Translated errors

* Disbale submti if bracket progression is bad

* Adjust bracketIdx

* changedBracketProgressionFormat

* Progress

* Fix E2E tests

* Docs progress

* Fix state change

* Add docs
2024-11-10 12:07:43 +02:00

199 lines
4.7 KiB
TypeScript

import { useFetcher } from "@remix-run/react";
import clsx from "clsx";
import { sub } from "date-fns";
import * as React from "react";
import { LinkButton } from "~/components/Button";
import { Popover } from "~/components/Popover";
import { SubmitButton } from "~/components/SubmitButton";
import { CheckmarkIcon } from "~/components/icons/Checkmark";
import { useUser } from "~/features/auth/core/user";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { logger } from "~/utils/logger";
import { tournamentMatchPage, tournamentRegisterPage } from "~/utils/urls";
export function TournamentTeamActions() {
const tournament = useTournament();
const user = useUser();
const fetcher = useFetcher();
const status = tournament.teamMemberOfProgressStatus(user);
if (!status) return null;
if (status.type === "MATCH") {
return (
<Container spaced="very">
vs. {status.opponent}
<LinkButton
to={tournamentMatchPage({
tournamentId: tournament.ctx.id,
matchId: status.matchId,
})}
variant="minimal"
size="tiny"
>
Go to match
</LinkButton>
</Container>
);
}
if (status.type === "CHECKIN") {
const bracket = tournament.brackets[status.bracketIdx ?? -1];
if (!bracket) {
return (
<Container spaced="very">
Your team needs to check-in
<fetcher.Form
method="post"
action={tournamentRegisterPage(tournament.ctx.id)}
>
<input type="hidden" name="bracketIdx" value={status.bracketIdx} />
{status.canCheckIn ? (
<SubmitButton
size="tiny"
variant="minimal"
_action="CHECK_IN"
state={fetcher.state}
testId="check-in-bracket-button"
>
Check-in now
</SubmitButton>
) : (
<Popover
buttonChildren={<>Check-in now</>}
triggerClassName="minimal tiny"
>
{tournament.ctx.mapPickingStyle !== "TO"
? "Can't check-in, registration needs to be finished by the captain (full roster & map pool picked)"
: "Can't check-in, registration needs to be finished by the captain (full roster)"}
</Popover>
)}
</fetcher.Form>
</Container>
);
}
return (
<Container spaced="very">
{bracket.name} check-in
{bracket.canCheckIn(user) ? (
<fetcher.Form method="post">
<input type="hidden" name="bracketIdx" value={status.bracketIdx} />
<SubmitButton
size="tiny"
variant="minimal"
_action="BRACKET_CHECK_IN"
state={fetcher.state}
testId="check-in-bracket-button"
>
Check-in
</SubmitButton>
</fetcher.Form>
) : bracket.startTime && bracket.startTime > new Date() ? (
<span className="text-lighter text-xxs" suppressHydrationWarning>
open{" "}
{sub(bracket.startTime, { hours: 1 }).toLocaleTimeString("en-US", {
hour: "numeric",
minute: "numeric",
weekday: "short",
})}{" "}
-{" "}
{bracket.startTime.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "numeric",
})}
</span>
) : bracket.startTime && bracket.startTime < new Date() ? (
<span className="text-warning">over</span>
) : null}
</Container>
);
}
if (status.type === "WAITING_FOR_MATCH") {
return (
<Container>
Waiting on match
<Dots />
</Container>
);
}
if (status.type === "WAITING_FOR_CAST") {
return (
<Container>
Waiting on cast
<Dots />
</Container>
);
}
if (status.type === "WAITING_FOR_ROUND") {
return (
<Container>
Waiting on next round
<Dots />
</Container>
);
}
if (status.type === "WAITING_FOR_BRACKET") {
return (
<Container spaced>
<CheckmarkIcon className="tournament-bracket__quick-action__checkmark" />{" "}
<div>
Checked in, waiting on bracket
<Dots />
</div>
</Container>
);
}
if (status.type === "THANKS_FOR_PLAYING") {
return <Container>Thank you for playing!</Container>;
}
logger.warn("Unexpected status", status);
return null;
}
function Container({
children,
spaced,
}: {
children: React.ReactNode;
spaced?: boolean | "very";
}) {
return (
<div
className={clsx("tournament-bracket__quick-action", {
"tournament-bracket__quick-action__spaced": spaced,
"tournament-bracket__quick-action__very-spaced": spaced === "very",
})}
>
{children}
</div>
);
}
function Dots() {
const [thirdVisible, setThirdVisible] = React.useState(false);
React.useEffect(() => {
const timeout = setInterval(() => {
setThirdVisible((prev) => !prev);
}, 1500);
return () => {
clearTimeout(timeout);
};
}, []);
return (
<span>
..<span className={clsx({ invisible: !thirdVisible })}>.</span>
</span>
);
}