Ladder initial with FAQ (#443)

* queries for play service

* seed ladder day

* seed ladder registered teams

* ladder day get for future

* seed add ladder matches

* extract RegisterTab component

* MyError / MySpinner

* tweaks

* FAQ tab

* add to faq

* stats tab

* tabs to search params

* tabs for mobile
This commit is contained in:
Kalle 2021-04-26 15:18:27 +03:00 committed by GitHub
parent c78f971b91
commit 89afd0e41d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 925 additions and 243 deletions

View File

@ -0,0 +1,12 @@
import { Alert, AlertIcon } from "@chakra-ui/alert";
const MyError = ({ message }: { message: string }) => {
return (
<Alert status="error" rounded="lg">
<AlertIcon />
{message}
</Alert>
);
};
export default MyError;

View File

@ -0,0 +1,9 @@
import { Spinner } from "@chakra-ui/spinner";
import { useMyTheme } from "hooks/common";
const MySpinner = () => {
const { themeColorShade } = useMyTheme();
return <Spinner color={themeColorShade} thickness="3px" />;
};
export default MySpinner;

View File

@ -0,0 +1,80 @@
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 { useMyTheme } from "hooks/common";
import { Fragment } from "react";
import { hoursToMilliseconds } from "utils/numbers";
import { trpc } from "utils/trpc";
import MatchUp from "./MatchUp";
const ActiveMatchesTab = () => {
const { gray } = useMyTheme();
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={gray}
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;

197
components/play/FAQTab.tsx Normal file
View File

@ -0,0 +1,197 @@
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;

View File

@ -79,7 +79,7 @@ const RegisterHeader: React.FC<Props> = ({}) => {
const ownTeamFullyRegisted = !!ownTeam && ownTeam.roster.length >= 4;
return (
<Box>
<Box my={6}>
{ownTeam ? (
<Alert
status={ownTeamFullyRegisted ? "success" : "warning"}
@ -89,7 +89,7 @@ const RegisterHeader: React.FC<Props> = ({}) => {
justifyContent="center"
textAlign="center"
p={6}
mt={4}
rounded="lg"
>
<AlertTitle mb={1} fontSize="lg">
{ownTeam.roster.length >= 4 ? (
@ -116,7 +116,7 @@ const RegisterHeader: React.FC<Props> = ({}) => {
h="1.75rem"
size="sm"
isDisabled={sending || copied}
my={2}
my={4}
variant="outline"
width={36}
>
@ -156,12 +156,7 @@ const RegisterHeader: React.FC<Props> = ({}) => {
</AlertDescription>
</Alert>
) : (
<Button
size="sm"
variant="outline"
onClick={createNewTeam}
isLoading={sending}
>
<Button onClick={createNewTeam} isLoading={sending}>
<Trans>Register new team</Trans>
</Button>
)}

View File

@ -0,0 +1,64 @@
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;

View File

@ -83,6 +83,11 @@ const extendedTheme = extendTheme({
colorScheme: "theme",
},
},
Tabs: {
defaultProps: {
colorScheme: "theme",
},
},
Modal: {
baseStyle: (props) => ({
dialog: {

View File

@ -3,6 +3,7 @@ import { inferAsyncReturnType, inferProcedureOutput } from "@trpc/server";
import * as trpcNext from "@trpc/server/adapters/next";
import calendarApi from "routers/calendar";
import freeAgentsApi from "routers/freeagents";
import playApi from "routers/play";
import plusApi from "routers/plus";
import superjson from "superjson";
import { getMySession } from "utils/api";
@ -22,7 +23,8 @@ export function createRouter() {
export const appRouter = createRouter()
.merge("plus.", plusApi)
.merge("calendar.", calendarApi)
.merge("freeAgents.", freeAgentsApi);
.merge("freeAgents.", freeAgentsApi)
.merge("play.", playApi);
// Exporting type _type_ AppRouter only exposes types that can be used for inference
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export

View File

@ -1,211 +1,183 @@
import { Box, Center, Flex, Grid, Heading } from "@chakra-ui/react";
import { Trans } from "@lingui/macro";
import ModeImage from "components/common/ModeImage";
import SubText from "components/common/SubText";
import LadderTeam from "components/play/LadderTeam";
import MatchUp from "components/play/MatchUp";
import RegisterHeader from "components/play/RegisterHeader";
import { useMyTheme } from "hooks/common";
import { useLadderTeams } from "hooks/play";
import { GetStaticProps } from "next";
import prisma from "prisma/client";
import { getAllLadderRegisteredTeamsForMatches } from "prisma/queries/getAllLadderRegisteredTeamsForMatches";
import { getLadderDay, GetLadderDayData } from "prisma/queries/getLadderDay";
import { Fragment } from "react";
import { shuffleArray } from "utils/arrays";
import { getLadderRounds } from "utils/play";
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
import ActiveMatchesTab from "components/play/ActiveMatchesTab";
import FAQTab from "components/play/FAQTab";
import RegisterTab from "components/play/RegisterTab";
import { useRouter } from "next/router";
import { useState } from "react";
import { setSearchParams } from "utils/setSearchParams";
interface Props {
ladderDay: GetLadderDayData;
}
const TAB_MAX_INDEX = 5;
const PlayPage: React.FC<Props> = ({ ladderDay }) => {
const { gray } = useMyTheme();
const { data } = useLadderTeams(!ladderDay);
const PlayPage = () => {
const router = useRouter();
const [tabIndex, setTabIndex] = useState(() => {
const tab = Number(router.query.tab);
if (Number.isNaN(tab) || tab < 0 || tab > TAB_MAX_INDEX) {
return 0;
}
return tab;
});
const handleTabIndexChange = (index: number) => {
setSearchParams("tab", "" + index);
setTabIndex(index);
};
return (
<>
{!ladderDay?.matches.length && (
<Box fontSize="lg" fontWeight="bold">
{ladderDay ? (
<>Next event: {new Date(ladderDay.date).toLocaleString()}</>
) : (
<>
<Trans>
Next ladder date is not confirmed. Follow this page for updates!
</Trans>
</>
)}
</Box>
)}
{ladderDay && ladderDay.matches.length === 0 && (
<>
<RegisterHeader />
<Box mt={4}>
{data
?.sort((a, b) => b.roster.length - a.roster.length)
.map((team) => (
<LadderTeam key={team.id} roster={team.roster} my={2} />
))}
</Box>
</>
)}
{ladderDay && ladderDay.matches.length > 0 && (
<>
{[1, 2].map((round) => (
<Fragment key={round}>
<Heading size="md">Round {round}</Heading>
<SubText mt={4}>Maplist</SubText>
{(ladderDay.matches.find((match) => match.order === round)!
.maplist as any[]).map(({ stage, mode }, i) => {
return (
<Flex
key={stage + mode}
align="center"
color={gray}
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}
>
{ladderDay.matches
.filter((match) => match.order === round)
.map((match) => (
<MatchUp
key={match.players[0].user.discordId}
matchUp={match}
/>
))}
</Grid>
</Fragment>
))}
</>
)}
<Tabs index={tabIndex} onChange={handleTabIndexChange}>
<TabList mb="1em" flexDir={["column", null, "row"]}>
<Tab>Register</Tab>
<Tab isDisabled={process.env.NODE_ENV === "production"}>
Active Matches
</Tab>
<Tab isDisabled>Match History</Tab>
<Tab isDisabled>Leaderboards</Tab>
<Tab isDisabled>Stats</Tab>
<Tab>FAQ</Tab>
</TabList>
<TabPanels>
<TabPanel p={1}>
<RegisterTab />
</TabPanel>
<TabPanel p={1}>
<ActiveMatchesTab />
</TabPanel>
<TabPanel p={1}>
<p>three!</p>
</TabPanel>
<TabPanel p={1}>
<p>four!</p>
</TabPanel>
<TabPanel p={1}>
<p>Stats!</p>
</TabPanel>
<TabPanel p={1}>
<FAQTab />
</TabPanel>
</TabPanels>
</Tabs>
</>
);
};
export const getStaticProps: GetStaticProps<Props> = async () => {
const ladderDay = await getLadderDay();
// export const getStaticProps: GetStaticProps<Props> = async () => {
// const [ladderDay, previousLadderDay] = await Promise.all([
// playService.nextLadderDay(),
// playService.previousLadderDay(),
// ]);
let ladderDayAfterGeneration: GetLadderDayData | undefined;
if (
ladderDay &&
!ladderDay.matches.length &&
ladderDay.date.getTime() < new Date().getTime()
) {
const teams = (await getAllLadderRegisteredTeamsForMatches()).filter(
(team) => team.roster.length === 4
);
// let ladderDayAfterGeneration: NextLadderDay | undefined;
// if (
// ladderDay &&
// !ladderDay.matches.length &&
// ladderDay.date.getTime() < new Date().getTime()
// ) {
// const teams = (await getAllLadderRegisteredTeamsForMatches()).filter(
// (team) => team.roster.length === 4
// );
if (teams.length < 4) {
return {
props: { ladderDay: JSON.parse(JSON.stringify(ladderDay)) },
revalidate: 30,
}; // FIX
}
// if (teams.length < 4) {
// return {
// props: {
// ladderDay: JSON.parse(JSON.stringify(ladderDay)),
// previousLadderDay,
// },
// revalidate: 30,
// }; // FIX
// }
const matches = getLadderRounds(teams);
// const matches = getLadderRounds(teams);
const eighteenMaps = getMaplist();
// const eighteenMaps = getMaplist();
const dateWeekFromNow = new Date(ladderDay.date);
dateWeekFromNow.setHours(
ladderDay.date.getFullYear(),
ladderDay.date.getMonth(),
ladderDay.date.getDate() + 7
);
// const dateWeekFromNow = new Date(ladderDay.date);
// dateWeekFromNow.setHours(
// ladderDay.date.getFullYear(),
// ladderDay.date.getMonth(),
// ladderDay.date.getDate() + 7
// );
const createMatches = matches.flatMap((round, i) =>
round.map((match) =>
prisma.ladderMatch.create({
data: {
dayId: ladderDay.id,
maplist:
i === 0 ? eighteenMaps.slice(0, 9) : eighteenMaps.slice(9, 18),
order: i + 1,
players: {
create: match.flatMap((team, teamI) =>
team.roster.map((user) => ({
userId: user.id,
team: teamI === 0 ? "ALPHA" : "BRAVO",
}))
),
},
},
})
)
);
const createLadderDay = prisma.ladderDay.create({
data: { date: dateWeekFromNow },
});
const deleteLadderRegisteredTeams = prisma.ladderRegisteredTeam.deleteMany();
// const createMatches = matches.flatMap((round, i) =>
// round.map((match) =>
// prisma.ladderMatch.create({
// data: {
// dayId: ladderDay.id,
// maplist:
// i === 0 ? eighteenMaps.slice(0, 9) : eighteenMaps.slice(9, 18),
// order: i + 1,
// players: {
// create: match.flatMap((team, teamI) =>
// team.roster.map((user) => ({
// userId: user.id,
// team: teamI === 0 ? "ALPHA" : "BRAVO",
// }))
// ),
// },
// },
// })
// )
// );
// const createLadderDay = prisma.ladderDay.create({
// data: { date: dateWeekFromNow },
// });
// const deleteLadderRegisteredTeams = prisma.ladderRegisteredTeam.deleteMany();
await prisma.$transaction([
...createMatches,
createLadderDay,
deleteLadderRegisteredTeams,
] as any);
// await prisma.$transaction([
// ...createMatches,
// createLadderDay,
// deleteLadderRegisteredTeams,
// ] as any);
ladderDayAfterGeneration = await getLadderDay();
}
// ladderDayAfterGeneration = await playService.nextLadderDay();
// }
return {
props: {
ladderDay: JSON.parse(
JSON.stringify(ladderDayAfterGeneration ?? ladderDay)
),
},
revalidate: 30,
};
};
// return {
// props: {
// ladderDay: JSON.parse(
// JSON.stringify(ladderDayAfterGeneration ?? ladderDay)
// ),
// previousLadderDay,
// },
// revalidate: 30,
// };
// };
function getMaplist() {
const modes = shuffleArray(["SZ", "TC", "RM", "CB"]);
const stages = shuffleArray([
"The Reef",
"Musselforge Fitness",
"Starfish Mainstage",
"Humpback Pump Track",
"Inkblot Art Academy",
"Sturgeon Shipyard",
"Manta Maria",
"Snapper Canal",
"Blackbelly Skatepark",
"MakoMart",
"Shellendorf Institute",
"Goby Arena",
"Piranha Pit",
"Camp Triggerfish",
"Wahoo World",
"New Albacore Hotel",
"Ancho-V Games",
"Skipper Pavilion",
//"Moray Towers",
//"Port Mackerel",
//"Walleye Warehouse",
//"Arowana Mall",
//"Kelp Dome"
]);
// function getMaplist() {
// const modes = shuffleArray(["SZ", "TC", "RM", "CB"]);
// const stages = shuffleArray([
// "The Reef",
// "Musselforge Fitness",
// "Starfish Mainstage",
// "Humpback Pump Track",
// "Inkblot Art Academy",
// "Sturgeon Shipyard",
// "Manta Maria",
// "Snapper Canal",
// "Blackbelly Skatepark",
// "MakoMart",
// "Shellendorf Institute",
// "Goby Arena",
// "Piranha Pit",
// "Camp Triggerfish",
// "Wahoo World",
// "New Albacore Hotel",
// "Ancho-V Games",
// "Skipper Pavilion",
// //"Moray Towers",
// //"Port Mackerel",
// //"Walleye Warehouse",
// //"Arowana Mall",
// //"Kelp Dome"
// ]);
return stages.map((stage) => {
const mode = modes.pop();
modes.unshift(mode!);
return { stage, mode };
});
}
// return stages.map((stage) => {
// const mode = modes.pop();
// modes.unshift(mode!);
// return { stage, mode };
// });
// }
export default PlayPage;

View File

@ -0,0 +1,18 @@
import { Prisma } from "@prisma/client";
import { Factory } from "fishery";
import { v4 as uuidv4 } from "uuid";
import prisma from "../client";
export default Factory.define<Prisma.LadderRegisteredTeamCreateManyInput>(
({ sequence, onCreate }) => {
onCreate((data) => {
return prisma.ladderRegisteredTeam.create({ data });
});
return {
id: sequence,
inviteCode: uuidv4(),
ownerId: sequence,
};
}
);

View File

@ -1,44 +0,0 @@
import { Prisma } from "@prisma/client";
import prisma from "prisma/client";
export type GetLadderDayData = Prisma.PromiseReturnType<typeof getLadderDay>;
export const getLadderDay = async () => {
const d = new Date();
d.setHours(d.getHours() - 6);
return prisma.ladderDay.findFirst({
where: { date: { gte: d } },
include: {
matches: {
select: {
order: true,
maplist: true,
teamAScore: true,
teamBScore: true,
players: {
select: {
team: true,
user: {
select: {
id: true,
discordAvatar: true,
discordId: true,
discriminator: true,
username: true,
team: {
select: {
name: true,
nameForUrl: true,
twitterName: true,
},
},
},
},
},
},
},
},
},
});
};

View File

@ -2,6 +2,7 @@ import fs from "fs";
import path from "path";
import prisma from "./client";
import calendarEventFactory from "./factories/calendarEvent";
import ladderRegisteredTeamFactory from "./factories/ladderRegisteredTeam";
import userFactory from "./factories/user";
import {
getPlusStatusesData,
@ -59,6 +60,7 @@ async function dropAllData() {
async function seedNewData() {
await seedUsers();
await seedEvents();
await seedLadderData();
await prisma.plusStatus.createMany({ data: getPlusStatusesData() });
await prisma.plusSuggestion.createMany({ data: getPlusSuggestionsData() });
await prisma.plusVotingSummary.createMany({
@ -115,6 +117,228 @@ async function seedEvents() {
});
}
async function seedLadderData() {
const twentyFourHoursFromNow = new Date(
new Date().getTime() + 24 * 60 * 60 * 1000
);
twentyFourHoursFromNow.setHours(12, 0, 0);
await prisma.ladderDay.createMany({
data: [
{
id: 1,
date: new Date(twentyFourHoursFromNow.getTime() - 24 * 60 * 60 * 1000),
},
{
id: 2,
date: twentyFourHoursFromNow,
},
],
});
await prisma.ladderRegisteredTeam.createMany({
data: [...Array(2)].map((_, _i) => {
return ladderRegisteredTeamFactory.build();
}),
});
await Promise.all([
prisma.user.updateMany({
where: { id: { in: [1, 2, 3, 4] } },
data: { ladderTeamId: 1 },
}),
prisma.user.updateMany({
where: { id: { in: [5, 6, 7] } },
data: { ladderTeamId: 2 },
}),
]);
await prisma.ladderMatch.create({
data: {
id: 1,
dayId: 1,
maplist: [
{
stage: "The Reef",
mode: "SZ",
},
{
stage: "Musselforge Fitness",
mode: "CB",
},
{
stage: "Starfish Mainstage",
mode: "SZ",
},
{
stage: "Humpback Pump Track",
mode: "TC",
},
{
stage: "Inkblot Art Academy",
mode: "SZ",
},
{
stage: "Sturgeon Shipyard",
mode: "RM",
},
{
stage: "Manta Maria",
mode: "SZ",
},
{
stage: "Snapper Canal",
mode: "CB",
},
{
stage: "Blackbelly Skatepark",
mode: "SZ",
},
],
order: 1,
teamAScore: 5,
teamBScore: 0,
},
});
// await prisma.ladderMatch.createMany({
// data: [
// {
// id: 1,
// dayId: 1,
// maplist: [
// {
// stage: "The Reef",
// mode: "SZ",
// },
// {
// stage: "Musselforge Fitness",
// mode: "CB",
// },
// {
// stage: "Starfish Mainstage",
// mode: "SZ",
// },
// {
// stage: "Humpback Pump Track",
// mode: "TC",
// },
// {
// stage: "Inkblot Art Academy",
// mode: "SZ",
// },
// {
// stage: "Sturgeon Shipyard",
// mode: "RM",
// },
// {
// stage: "Manta Maria",
// mode: "SZ",
// },
// {
// stage: "Snapper Canal",
// mode: "CB",
// },
// {
// stage: "Blackbelly Skatepark",
// mode: "SZ",
// },
// ],
// order: 1,
// teamAScore: 5,
// teamBScore: 0,
// },
// {
// id: 2,
// dayId: 1,
// maplist: [
// {
// stage: "MakoMart",
// mode: "SZ",
// },
// {
// stage: "Shellendorf Institute",
// mode: "TC",
// },
// {
// stage: "Goby Arena",
// mode: "SZ",
// },
// {
// stage: "Piranha Pit",
// mode: "CB",
// },
// {
// stage: "Camp Triggerfish",
// mode: "SZ",
// },
// {
// stage: "Wahoo World",
// mode: "RM",
// },
// {
// stage: "New Albacore Hotel",
// mode: "SZ",
// },
// {
// stage: "Ancho-V Games",
// mode: "TC",
// },
// {
// stage: "Skipper Pavilion",
// mode: "SZ",
// },
// ],
// order: 2,
// },
// ],
// });
await prisma.ladderMatchPlayer.createMany({
data: [
{
matchId: 1,
team: "ALPHA",
userId: 1,
},
{
matchId: 1,
team: "ALPHA",
userId: 2,
},
{
matchId: 1,
team: "ALPHA",
userId: 3,
},
{
matchId: 1,
team: "ALPHA",
userId: 4,
},
{
matchId: 1,
team: "BRAVO",
userId: 5,
},
{
matchId: 1,
team: "BRAVO",
userId: 6,
},
{
matchId: 1,
team: "BRAVO",
userId: 7,
},
{
matchId: 1,
team: "BRAVO",
userId: 8,
},
],
});
}
main()
.catch((e) => {
console.error(e);

20
routers/play.ts Normal file
View File

@ -0,0 +1,20 @@
import { createRouter } from "pages/api/trpc/[trpc]";
import service from "services/play";
const playApi = createRouter()
.query("allRegisteredTeams", {
resolve() {
return service.allRegisteredTeams();
},
})
.query("nextLadderDay", {
resolve() {
return service.nextLadderDay();
},
})
.query("previousLadderDay", {
resolve() {
return service.previousLadderDay();
},
});
export default playApi;

126
services/play.ts Normal file
View File

@ -0,0 +1,126 @@
import { Prisma } from ".prisma/client";
import prisma from "prisma/client";
const allRegisteredTeams = async () =>
prisma.ladderRegisteredTeam.findMany({
include: {
roster: {
select: {
id: true,
discordAvatar: true,
discordId: true,
discriminator: true,
username: true,
team: {
select: {
name: true,
nameForUrl: true,
twitterName: true,
},
},
},
},
},
});
const allLadderRegisteredTeamsForMatches = async () =>
prisma.ladderRegisteredTeam.findMany({
select: {
id: true,
roster: {
select: {
id: true,
trueSkill: true,
},
},
},
});
export type NextLadderDay = Prisma.PromiseReturnType<typeof nextLadderDay>;
const nextLadderDay = async () => {
return prisma.ladderDay.findFirst({
where: { date: { gte: new Date() } },
include: {
matches: {
select: {
order: true,
maplist: true,
teamAScore: true,
teamBScore: true,
players: {
select: {
team: true,
user: {
select: {
id: true,
discordAvatar: true,
discordId: true,
discriminator: true,
username: true,
team: {
select: {
name: true,
nameForUrl: true,
twitterName: true,
},
},
},
},
},
},
},
},
},
});
};
export type PreviousLadderDay = Prisma.PromiseReturnType<
typeof previousLadderDay
>;
const previousLadderDay = async () => {
return prisma.ladderDay.findFirst({
where: { date: { lt: new Date() } },
orderBy: { date: "desc" },
select: {
date: true,
matches: {
select: {
order: true,
maplist: true,
teamAScore: true,
teamBScore: true,
players: {
select: {
team: true,
user: {
select: {
id: true,
discordAvatar: true,
discordId: true,
discriminator: true,
username: true,
team: {
select: {
name: true,
nameForUrl: true,
twitterName: true,
},
},
},
},
},
},
},
},
},
});
};
export default {
allRegisteredTeams,
allLadderRegisteredTeamsForMatches,
nextLadderDay,
previousLadderDay,
};

View File

@ -1 +1,3 @@
export const truncuateFloat = (float: number) => parseFloat(float.toFixed(1));
export const hoursToMilliseconds = (hours: number) => hours * 1000 * 3600;