diff --git a/components/common/NewTable.tsx b/components/common/NewTable.tsx index 2b9fcb6e8..8eb9b2948 100644 --- a/components/common/NewTable.tsx +++ b/components/common/NewTable.tsx @@ -34,7 +34,7 @@ export default function NewTable({ if (isSmall) { return ( <> - {data.map((row) => { + {data.map((row, i) => { return ( @@ -85,7 +86,7 @@ export default function NewTable({ - {data.map((row) => { + {data.map((row, i) => { return ( {headers.map(({ dataKey }) => { diff --git a/components/layout/NavButtons.tsx b/components/layout/NavButtons.tsx index 959683cd8..886123416 100644 --- a/components/layout/NavButtons.tsx +++ b/components/layout/NavButtons.tsx @@ -14,7 +14,7 @@ const NavButtons = ({ onButtonClick }: { onButtonClick?: () => void }) => { return ( { + switch (props.type) { + case "LEAGUE": + const addToHeaders = + props.placements[0].members.length === 4 + ? [ + { name: "member 3", dataKey: "member3" }, + { name: "member 4", dataKey: "member4" }, + ] + : []; + return ( + { + const members = placement.members.reduce( + (acc: Record, member, i) => { + const name = + member.player.user?.username ?? member.player.name ?? "???"; + acc[`member${i + 1}`] = ( + + {member.player.user ? ( + + + + ) : null} + + {name} + + + ); + + return acc; + }, + {} + ); + return { + id: placement.id, + leaguePower: placement.leaguePower, + time: new Date(placement.startTime).toLocaleString("en", { + month: "numeric", + year: "2-digit", + hour: "numeric", + day: "numeric", + }), + weapons: ( + + {placement.members.map((member) => ( + + ))} + + ), + ...members, + }; + })} + /> + ); + case "XPOWER_PEAK": + return ( + { + return { + id: placement.id, + name: ( + + {placement.player.user ? ( + + + + ) : null} + + {placement.playerName} + + + ), + xPower: placement.xPower, + weapon: , + mode: , + month: `${placement.month}/${placement.year}`, + }; + })} + /> + ); + default: + throw Error("invalid leaderboard type"); + } +}; + +export default LeaderboardTable; diff --git a/pages/index.tsx b/pages/index.tsx index a4daf71d5..57ce3d55a 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -2,13 +2,12 @@ import { Box, Flex, Heading, useColorMode } from "@chakra-ui/react"; import { Trans } from "@lingui/macro"; import MyLink from "components/common/MyLink"; import NavButtons from "components/layout/NavButtons"; -import { useMyTheme, useUser } from "hooks/common"; +import { useMyTheme } from "hooks/common"; import Image from "next/image"; const HomePage = () => { - const { bgColor, secondaryBgColor, gray } = useMyTheme(); + const { gray } = useMyTheme(); const { colorMode } = useColorMode(); - const [user] = useUser(); return ( <> diff --git a/pages/leaderboards/[[...slug]].tsx b/pages/leaderboards/[[...slug]].tsx new file mode 100644 index 000000000..be63c51fd --- /dev/null +++ b/pages/leaderboards/[[...slug]].tsx @@ -0,0 +1,137 @@ +import { LeagueType, RankedMode, Region } from ".prisma/client"; +import { Select } from "@chakra-ui/select"; +import MyHead from "components/common/MyHead"; +import LeaderboardTable from "components/leaderboards/LeaderboardTable"; +import { GetStaticPaths, GetStaticProps } from "next"; +import { useRouter } from "next/router"; +import leaderboardsService, { + Peak, + PeakByWeapon, + PeakLeague, +} from "services/leaderboards"; +import { weapons } from "utils/lists/weapons"; + +export type LeaderboardsPageProps = + | { + placements: Peak | PeakByWeapon; + type: "XPOWER_PEAK"; + } + | { + placements: PeakLeague; + type: "LEAGUE"; + }; + +export const LeaderboardsPage = (props: LeaderboardsPageProps) => { + const router = useRouter(); + + const dropdownValue = + router.asPath.replace("/leaderboards", "").length === 0 + ? "ALL" + : decodeURI(router.asPath.split("/")[2]); + + return ( + <> + + + + + ); +}; + +export const getStaticPaths: GetStaticPaths = async () => { + return { paths: [], fallback: "blocking" }; +}; + +export const getStaticProps: GetStaticProps = async ({ + params, +}) => { + const slug = params?.slug; + if ( + typeof slug === "string" || + (typeof slug === "object" && slug.length !== 1) + ) + return { notFound: true }; + + if (!slug) { + return { + props: { + type: "XPOWER_PEAK", + placements: await leaderboardsService.peak(), + }, + }; + } + + if (/^(SZ|TC|RM|CB)$/.test(slug[0])) { + return { + props: { + type: "XPOWER_PEAK", + placements: await leaderboardsService.peak(slug[0] as RankedMode), + }, + }; + } + + if (/^(LEAGUE)-(TWIN|QUAD)-(EU|NA|JP)$/.test(slug[0])) { + const [, type, region] = slug[0].split("-"); + + return { + props: { + type: "LEAGUE", + placements: JSON.parse( + JSON.stringify( + await leaderboardsService.peakLeague({ + type: type as LeagueType, + region: region as Region, + }) + ) + ), + }, + }; + } + + if (!weapons.includes(slug[0] as any)) return { notFound: true }; + + return { + props: { + type: "XPOWER_PEAK", + placements: await leaderboardsService.peakByWeapon(slug[0]), + }, + }; +}; + +function leaderboardTypeToHref(value: string) { + switch (value) { + case "ALL": + return "/leaderboards"; + default: + return `/leaderboards/${value}`; + } +} + +export default LeaderboardsPage; diff --git a/services/leaderboards.ts b/services/leaderboards.ts new file mode 100644 index 000000000..c6478743d --- /dev/null +++ b/services/leaderboards.ts @@ -0,0 +1,107 @@ +import { LeagueType, Prisma, RankedMode, Region } from ".prisma/client"; +import prisma from "prisma/client"; +import { userBasicSelection } from "utils/prisma"; + +export type PeakByWeapon = Prisma.PromiseReturnType; + +const peakByWeapon = (weapon: string) => { + return prisma.xRankPlacement.findMany({ + distinct: ["switchAccountId"], + orderBy: { + xPower: "desc", + }, + where: { weapon }, + select: { + id: true, + mode: true, + month: true, + playerName: true, + switchAccountId: true, + xPower: true, + year: true, + weapon: true, + player: { + select: { + user: { + select: userBasicSelection, + }, + }, + }, + }, + take: 100, + }); +}; + +export type Peak = Prisma.PromiseReturnType; + +const peak = (mode?: RankedMode) => { + return prisma.xRankPlacement.findMany({ + distinct: ["switchAccountId"], + orderBy: [ + { + xPower: "desc", + }, + { year: "asc" }, + { month: "asc" }, + ], + select: { + id: true, + mode: true, + month: true, + playerName: true, + switchAccountId: true, + xPower: true, + year: true, + weapon: true, + player: { + select: { + user: { + select: userBasicSelection, + }, + }, + }, + }, + where: { mode }, + take: 100, + }); +}; + +export type PeakLeague = Prisma.PromiseReturnType; + +const peakLeague = ({ region, type }: { region: Region; type: LeagueType }) => { + return prisma.leagueSquad.findMany({ + orderBy: [ + { + leaguePower: "desc", + }, + { startTime: "asc" }, + ], + select: { + id: true, + leaguePower: true, + startTime: true, + members: { + select: { + switchAccountId: true, + weapon: true, + player: { + select: { + name: true, + user: { + select: userBasicSelection, + }, + }, + }, + }, + }, + }, + where: { region, type }, + take: 100, + }); +}; + +export default { + peak, + peakByWeapon, + peakLeague, +}; diff --git a/utils/constants.ts b/utils/constants.ts index 8040941da..7ec0e6d66 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -29,6 +29,7 @@ export const navItems: { }[] = [ { code: "xsearch", name: "Browser" }, { code: "xtrends", name: "Tier List" }, + { code: "leaderboards", name: "Leaderboards" }, { code: "sr", name: "Salmon Run",