mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 15:48:23 -05:00
Remove more /play components
This commit is contained in:
@@ -1,81 +0,0 @@
|
||||
import { Box, Center, Flex, Grid, Heading } from "@chakra-ui/layout";
|
||||
import ModeImage from "components/common/ModeImage";
|
||||
import MyError from "components/common/MyError";
|
||||
import MySpinner from "components/common/MySpinner";
|
||||
import SubText from "components/common/SubText";
|
||||
import { CSSVariables } from "utils/CSSVariables";
|
||||
import { Fragment } from "react";
|
||||
import { hoursToMilliseconds } from "utils/numbers";
|
||||
import { trpc } from "utils/trpc";
|
||||
import MatchUp from "./MatchUp";
|
||||
|
||||
const ActiveMatchesTab = () => {
|
||||
const previousLadderDayQuery = trpc.useQuery(["play.previousLadderDay"]);
|
||||
|
||||
if (previousLadderDayQuery.isLoading) return <MySpinner />;
|
||||
if (previousLadderDayQuery.error)
|
||||
return <MyError message={previousLadderDayQuery.error.message} />;
|
||||
|
||||
const previousLadderDay = previousLadderDayQuery.data!;
|
||||
|
||||
return (
|
||||
<>
|
||||
{previousLadderDay && previousLadderDay.matches.length > 0 && (
|
||||
<>
|
||||
{[1].map((round) => (
|
||||
<Fragment key={round}>
|
||||
<Heading size="md">Round {round}</Heading>
|
||||
<Box fontSize="sm" fontWeight="bold">
|
||||
{new Date(
|
||||
new Date(previousLadderDay.date).getTime() +
|
||||
hoursToMilliseconds(round - 1)
|
||||
).toLocaleString()}{" "}
|
||||
({Intl.DateTimeFormat().resolvedOptions().timeZone})
|
||||
</Box>
|
||||
<SubText mt={4}>Maplist</SubText>
|
||||
{(
|
||||
previousLadderDay.matches.find(
|
||||
(match) => match.order === round
|
||||
)!.maplist as any[]
|
||||
).map(({ stage, mode }, i) => {
|
||||
return (
|
||||
<Flex
|
||||
key={stage + mode}
|
||||
align="center"
|
||||
color={CSSVariables.themeGray}
|
||||
fontSize="sm"
|
||||
my={2}
|
||||
>
|
||||
{i + 1}){" "}
|
||||
<Center mx={1}>
|
||||
<ModeImage mode={mode} size={24} />
|
||||
</Center>{" "}
|
||||
{stage}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
<Grid
|
||||
templateColumns="3fr 1fr 3fr"
|
||||
placeItems="center"
|
||||
rowGap={4}
|
||||
mt={8}
|
||||
mb={8}
|
||||
>
|
||||
{previousLadderDay.matches
|
||||
.filter((match) => match.order === round)
|
||||
.map((match) => (
|
||||
<MatchUp
|
||||
key={match.players[0].user.discordId}
|
||||
matchUp={match}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActiveMatchesTab;
|
||||
@@ -1,196 +0,0 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
} from "@chakra-ui/accordion";
|
||||
import { Box } from "@chakra-ui/layout";
|
||||
import MyLink from "components/common/MyLink";
|
||||
|
||||
/*
|
||||
// //"Moray Towers",
|
||||
// //"Port Mackerel",
|
||||
// //"Walleye Warehouse",
|
||||
// //"Arowana Mall",
|
||||
// //"Kelp Dome"
|
||||
*/
|
||||
|
||||
const questionsAndAnswers = [
|
||||
{
|
||||
q: "What is the schedule?",
|
||||
a: (
|
||||
<>
|
||||
During the beta testing phase follow Sendou's Twitter for updates.
|
||||
You can choose when to play - there is no commitment. <br />
|
||||
<br />
|
||||
The plan after the beta testing phase is to have 5 event dates a week
|
||||
when you can play: Monday and Wednesday Americas friendly times (8PM
|
||||
ET). Tuesday and Thursday Europe friendly times (8PM CET). Sunday for a
|
||||
time that is aimed for both regions (9PM CET).
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "How many matches do we play when we register for an event?",
|
||||
a: (
|
||||
<>
|
||||
If there is an even number of teams everyone plays 2 matches. If there
|
||||
is an uneven number of teams then two teams play 1 match each and the
|
||||
rest play 2 matches.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Can I switch who I play with between the event dates? Can I sign up without a team?",
|
||||
a: (
|
||||
<>
|
||||
You can play with a different set of people each time you play the
|
||||
ladder if you want. However you need to find people to play with before
|
||||
signing up (so it doesn't work like solo queue in that sense).
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "What this the format of the matches?",
|
||||
a: (
|
||||
<>
|
||||
Best of 9. Even numbered weeks SZ/TC/RM/CB but Splat Zones is the every
|
||||
other mode. Uneven numbered weeks SZ only.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "What is the maplist used?",
|
||||
a: <>Every map except Moray, Port, Walleye, Mall & Kelp Dome is in play.</>,
|
||||
},
|
||||
{
|
||||
q: "What ranking system is used?",
|
||||
a: (
|
||||
<>
|
||||
You can read more about the ranking system used{" "}
|
||||
<MyLink
|
||||
isExternal
|
||||
href="https://www.microsoft.com/en-us/research/project/trueskill-ranking-system"
|
||||
>
|
||||
here
|
||||
</MyLink>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "How are the teams matched?",
|
||||
a: (
|
||||
<>
|
||||
The algorithm goes through every possible way to pair the teams that are
|
||||
signed up. It chooses the match-ups based on which two ways without
|
||||
repeat matches produce the best quality of matches. Quality of match is
|
||||
determined by the theoretical likelihood of a draw as provided by the
|
||||
ranking system we use.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Why are players ranked and not teams? What does this mean in practice?",
|
||||
a: (
|
||||
<>
|
||||
In Splatoon teams are pretty volatile. It's common for people to
|
||||
play with a lot of different people. Even in one team there is typically
|
||||
a big difference in level depending on who from the roster is playing.
|
||||
Ranking players lets us have more accurate rankings in these conditions.
|
||||
<br />
|
||||
<br />
|
||||
If you always play with the same people the ranking system won't be
|
||||
able to make a difference between your scores. The difference only comes
|
||||
if you play with different people also.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "DC rules?",
|
||||
a: (
|
||||
<>
|
||||
One DC replay per team per set is allowed. If DC happens before the game
|
||||
started (counted from players being able to move) room should be remade
|
||||
without DC replays being used. DC replay is only granted if it was less
|
||||
than 2 minutes 30 seconds into the match AND the team's score
|
||||
without DC didn't pass the 50 point mark AND the team with the DC
|
||||
stopped playing without delay.
|
||||
<br />
|
||||
<br /> If host DC's during game the hosting team has to use their
|
||||
DC replay if they have any left otherwise they lose the map.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Who hosts?",
|
||||
a: (
|
||||
<>
|
||||
If you can't agree which team should host then the team who is on
|
||||
the left side chooses a player in their team to host the room.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "How do I report the score?",
|
||||
a: (
|
||||
<>
|
||||
You report the score by DM'ing the bot Lanista#5266. The bot can be
|
||||
found on our{" "}
|
||||
<MyLink isExternal href="https://discord.gg/sendou">
|
||||
Discord
|
||||
</MyLink>
|
||||
. If you have not registed your Nintendo account with Lanista you have
|
||||
to use the <code>!register</code> command before. After that reporting
|
||||
the score is done with the <code>!sendoureport</code> command. <br />
|
||||
<br />
|
||||
If you have problems please contact Lean#3146 on Discord.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Can we get a sub?",
|
||||
a: (
|
||||
<>
|
||||
You need to play the sets with the 4 people you sign up with. If a
|
||||
player becomes unable to play then the rest of the matches need to be
|
||||
forfeited.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "How do I report a forfeit?",
|
||||
a: (
|
||||
<>
|
||||
You can do on our{" "}
|
||||
<MyLink isExternal href="https://discord.gg/sendou">
|
||||
Discord
|
||||
</MyLink>{" "}
|
||||
on the #helpdesk channel.
|
||||
</>
|
||||
),
|
||||
},
|
||||
] as const;
|
||||
|
||||
const FAQTab = () => {
|
||||
return (
|
||||
<Accordion>
|
||||
{questionsAndAnswers.map(({ q, a }) => (
|
||||
<AccordionItem key={q}>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box flex="1" textAlign="left" fontWeight="bold">
|
||||
{q}
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel pb={4}>{a}</AccordionPanel>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export default FAQTab;
|
||||
@@ -1,71 +0,0 @@
|
||||
import { Box, BoxProps, Flex, HStack } from "@chakra-ui/react";
|
||||
import SubText from "components/common/SubText";
|
||||
import TwitterAvatar from "components/common/TwitterAvatar";
|
||||
import UserAvatar from "components/common/UserAvatar";
|
||||
import { CSSVariables } from "utils/CSSVariables";
|
||||
import { GetAllLadderRegisteredTeamsData } from "prisma/queries/getAllLadderRegisteredTeams";
|
||||
import { Unpacked } from "utils/types";
|
||||
|
||||
interface Props {
|
||||
roster: Unpacked<GetAllLadderRegisteredTeamsData>["roster"];
|
||||
}
|
||||
|
||||
const LadderTeam: React.FC<Props & BoxProps> = ({ roster, ...props }) => {
|
||||
const teamTuple = roster
|
||||
.filter((member) => member.team)
|
||||
.map((member) => member.team)
|
||||
.reduce(
|
||||
(
|
||||
acc: [
|
||||
{
|
||||
name: string;
|
||||
twitterName: string;
|
||||
nameForUrl: string;
|
||||
},
|
||||
number
|
||||
][],
|
||||
cur
|
||||
) => {
|
||||
const tuple = acc.find(([{ name }]) => name === cur!.name);
|
||||
if (tuple) tuple[1]++;
|
||||
|
||||
acc.push([{ ...(cur as any) }, 1]);
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
)
|
||||
.find((tuple) => tuple[1] >= 3);
|
||||
|
||||
return (
|
||||
<Box {...props}>
|
||||
{teamTuple && roster.length === 4 ? (
|
||||
<Flex fontWeight="bold" align="center">
|
||||
{teamTuple[0].twitterName && (
|
||||
<TwitterAvatar
|
||||
twitterName={teamTuple[0].twitterName}
|
||||
size="sm"
|
||||
mr={1}
|
||||
/>
|
||||
)}
|
||||
{teamTuple[0].name}
|
||||
{teamTuple[1] === 3 && roster.length >= 4 && (
|
||||
<SubText ml={1}>+1</SubText>
|
||||
)}
|
||||
</Flex>
|
||||
) : (
|
||||
<HStack>
|
||||
{roster.map((member) => (
|
||||
<UserAvatar key={member.id} user={member} size="sm" />
|
||||
))}
|
||||
</HStack>
|
||||
)}
|
||||
<Box color={CSSVariables.themeGray} fontSize="sm" mt={2}>
|
||||
{roster
|
||||
.map((user) => `${user.username}#${user.discriminator}`)
|
||||
.join(", ")}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LadderTeam;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Box } from "@chakra-ui/react";
|
||||
import { PreviousLadderDay } from "services/play";
|
||||
import { Unpacked } from "utils/types";
|
||||
import LadderTeam from "./LadderTeam";
|
||||
|
||||
interface Props {
|
||||
matchUp: Unpacked<NonNullable<PreviousLadderDay>["matches"]>;
|
||||
}
|
||||
|
||||
const MatchUp: React.FC<Props> = ({ matchUp }) => {
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<LadderTeam
|
||||
roster={matchUp.players
|
||||
.filter((player) => player.team === "ALPHA")
|
||||
.map((player) => player.user)}
|
||||
/>
|
||||
</Box>
|
||||
<Box fontSize="xl">
|
||||
{matchUp.teamAScore}-{matchUp.teamBScore}
|
||||
</Box>
|
||||
<Box>
|
||||
<LadderTeam
|
||||
roster={matchUp.players
|
||||
.filter((player) => player.team === "BRAVO")
|
||||
.map((player) => player.user)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchUp;
|
||||
@@ -1,167 +0,0 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
Box,
|
||||
Button,
|
||||
Input,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { useUser } from "hooks/common";
|
||||
import { useLadderTeams } from "hooks/play";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FiCheck, FiTrash } from "react-icons/fi";
|
||||
import { getToastOptions } from "utils/objects";
|
||||
import { sendData } from "utils/postData";
|
||||
|
||||
interface Props {}
|
||||
|
||||
const RegisterHeader: React.FC<Props> = ({}) => {
|
||||
const toast = useToast();
|
||||
const [user] = useUser();
|
||||
const { data, mutate } = useLadderTeams();
|
||||
|
||||
const [sending, setSending] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!copied) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 750);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [copied]);
|
||||
|
||||
const createNewTeam = async () => {
|
||||
setSending(true);
|
||||
const success = await sendData("POST", "/api/play/teams");
|
||||
setSending(false);
|
||||
if (!success) return;
|
||||
|
||||
mutate();
|
||||
|
||||
toast(getToastOptions(t`Team created`, "success"));
|
||||
};
|
||||
|
||||
const deleteTeam = async () => {
|
||||
if (!window.confirm(t`Delete registration?`)) return;
|
||||
setSending(true);
|
||||
const success = await sendData("DELETE", "/api/play/teams");
|
||||
setSending(false);
|
||||
if (!success) return;
|
||||
|
||||
mutate();
|
||||
|
||||
toast(getToastOptions(t`Registration canceled`, "success"));
|
||||
};
|
||||
|
||||
const leaveTeam = async () => {
|
||||
if (!window.confirm(t`Leave team?`)) return;
|
||||
setSending(true);
|
||||
const success = await sendData("POST", "/api/play/teams/leave");
|
||||
setSending(false);
|
||||
if (!success) return;
|
||||
|
||||
mutate();
|
||||
|
||||
toast(getToastOptions(t`Team left`, "success"));
|
||||
};
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
const ownTeam = data?.find((team) =>
|
||||
team.roster.some((member) => member.id === user.id)
|
||||
);
|
||||
|
||||
const ownTeamFullyRegisted = !!ownTeam && ownTeam.roster.length >= 4;
|
||||
|
||||
return (
|
||||
<Box my={6}>
|
||||
{ownTeam ? (
|
||||
<Alert
|
||||
status={ownTeamFullyRegisted ? "success" : "warning"}
|
||||
variant="subtle"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
textAlign="center"
|
||||
p={6}
|
||||
rounded="lg"
|
||||
>
|
||||
<AlertTitle mb={1} fontSize="lg">
|
||||
{ownTeam.roster.length >= 4 ? (
|
||||
<Trans>Team fully registered</Trans>
|
||||
) : (
|
||||
<Trans>Add players to complete registration</Trans>
|
||||
)}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{ownTeam.inviteCode && ownTeam.roster.length < 4 && (
|
||||
<Box mt={2}>
|
||||
<Input
|
||||
name="code"
|
||||
value={`https://sendou.ink/play/join?code=${ownTeam.inviteCode}`}
|
||||
readOnly
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`https://sendou.ink/play/join?code=${ownTeam.inviteCode}`
|
||||
);
|
||||
setCopied(true);
|
||||
}}
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
isDisabled={sending || copied}
|
||||
my={4}
|
||||
variant="outline"
|
||||
width={36}
|
||||
>
|
||||
{copied ? <FiCheck /> : <Trans>Copy invite link</Trans>}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Box fontWeight="bold" mt={2}>
|
||||
{ownTeam.roster
|
||||
.map((member) => `${member.username}#${member.discriminator}`)
|
||||
.join(", ")}
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
{ownTeam.ownerId === user.id ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftIcon={<FiTrash />}
|
||||
colorScheme="red"
|
||||
onClick={deleteTeam}
|
||||
isLoading={sending}
|
||||
>
|
||||
<Trans>Delete registration</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
colorScheme="red"
|
||||
onClick={leaveTeam}
|
||||
isLoading={sending}
|
||||
>
|
||||
<Trans>Leave team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<Button onClick={createNewTeam} isLoading={sending}>
|
||||
<Trans>Register new team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterHeader;
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Box, Heading } from "@chakra-ui/layout";
|
||||
import MyError from "components/common/MyError";
|
||||
import MySpinner from "components/common/MySpinner";
|
||||
import { trpc } from "utils/trpc";
|
||||
import LadderTeam from "./LadderTeam";
|
||||
import RegisterHeader from "./RegisterHeader";
|
||||
|
||||
const RegisterTab = () => {
|
||||
const registeredTeamsQuery = trpc.useQuery(["play.allRegisteredTeams"]);
|
||||
const nextLadderDayQuery = trpc.useQuery(["play.nextLadderDay"]);
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
return (
|
||||
<Box fontWeight="bold">
|
||||
<>
|
||||
First ladder test date is planned for 8th May. Follow Sendou's
|
||||
Twitter for updates!
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (nextLadderDayQuery.isLoading) return <MySpinner />;
|
||||
if (nextLadderDayQuery.error)
|
||||
return <MyError message={nextLadderDayQuery.error.message} />;
|
||||
|
||||
const nextLadderDay = nextLadderDayQuery.data!;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!nextLadderDay?.matches.length && (
|
||||
<Box fontWeight="bold">
|
||||
{nextLadderDay ? (
|
||||
<>
|
||||
Next ladder event takes place at{" "}
|
||||
{new Date(nextLadderDay.date).toLocaleString()}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Next ladder date is not confirmed. Follow this page for updates!
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{nextLadderDay && (
|
||||
<>
|
||||
<RegisterHeader />
|
||||
<Box mt={4}>
|
||||
<Heading size="sm" as="h3">
|
||||
Registered teams
|
||||
</Heading>
|
||||
{registeredTeamsQuery.data
|
||||
?.sort((a, b) => b.roster.length - a.roster.length)
|
||||
.map((team) => (
|
||||
<LadderTeam key={team.id} roster={team.roster} my={2} />
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterTab;
|
||||
Reference in New Issue
Block a user