mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 09:20:24 -05:00
301 lines
8.3 KiB
TypeScript
301 lines
8.3 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Center,
|
|
Divider,
|
|
Flex,
|
|
Grid,
|
|
Heading,
|
|
IconButton,
|
|
Popover,
|
|
PopoverArrow,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
Stack,
|
|
useToast,
|
|
} from "@chakra-ui/react";
|
|
import { t, Trans } from "@lingui/macro";
|
|
import Markdown from "components/common/Markdown";
|
|
import SubText from "components/common/SubText";
|
|
import SubTextCollapse from "components/common/SubTextCollapse";
|
|
import TwitterAvatar from "components/common/TwitterAvatar";
|
|
import UserAvatar from "components/common/UserAvatar";
|
|
import WeaponImage from "components/common/WeaponImage";
|
|
import RosterPlayerBar from "components/t/RosterPlayerBar";
|
|
import TeamManagementModal from "components/t/TeamManagementModal";
|
|
import TeamProfileModal from "components/t/TeamProfileModal";
|
|
import { useUser } from "hooks/common";
|
|
import { GetStaticPaths, GetStaticProps } from "next";
|
|
import Image from "next/image";
|
|
import { getTeam, GetTeamData } from "prisma/queries/getTeam";
|
|
import { Fragment, useEffect, useState } from "react";
|
|
import { FaTwitter } from "react-icons/fa";
|
|
import { FiEdit } from "react-icons/fi";
|
|
import useSWR, { useSWRConfig } from "swr";
|
|
import { CSSVariables } from "utils/CSSVariables";
|
|
import { getToastOptions } from "utils/objects";
|
|
import { sendData } from "utils/postData";
|
|
import MyHead from "../../components/common/MyHead";
|
|
|
|
interface Props {
|
|
team: GetTeamData;
|
|
}
|
|
|
|
const getTeamXPInfo = (roster: NonNullable<GetTeamData>["roster"]) => {
|
|
const placements = roster!.reduce(
|
|
(
|
|
acc: {
|
|
weapon: string;
|
|
month: number;
|
|
year: number;
|
|
mode: "SZ" | "TC" | "RM" | "CB";
|
|
xPower: number;
|
|
username: string;
|
|
discriminator: string;
|
|
discordAvatar: string | null;
|
|
discordId: string;
|
|
}[],
|
|
cur
|
|
) => {
|
|
const placement = cur.player?.placements[0];
|
|
if (!placement) return acc;
|
|
|
|
if (acc.length < 4)
|
|
acc.push({
|
|
...placement,
|
|
username: cur.username,
|
|
discriminator: cur.discriminator,
|
|
discordAvatar: cur.discordAvatar,
|
|
discordId: cur.discordId,
|
|
});
|
|
else {
|
|
acc.sort((a, b) => b.xPower - a.xPower);
|
|
|
|
if (acc[3].xPower < placement.xPower) {
|
|
acc[3] = {
|
|
...placement,
|
|
username: cur.username,
|
|
discriminator: cur.discriminator,
|
|
discordAvatar: cur.discordAvatar,
|
|
discordId: cur.discordId,
|
|
};
|
|
}
|
|
}
|
|
return acc;
|
|
},
|
|
[]
|
|
);
|
|
|
|
return {
|
|
placements: placements.sort((a, b) => b.xPower - a.xPower),
|
|
teamXP: (
|
|
(placements.reduce((acc, cur) => acc + cur.xPower, 0) +
|
|
2000 * (4 - placements.length)) /
|
|
4
|
|
)
|
|
.toFixed(1)
|
|
.replace(".0", ""),
|
|
};
|
|
};
|
|
|
|
const TeamPage: React.FC<Props> = (props) => {
|
|
const { data } = useSWR<GetTeamData>(`/api/teams/${props.team!.id}`, {
|
|
fallbackData: props.team!,
|
|
});
|
|
const team = data!;
|
|
|
|
const [sending, setSending] = useState(false);
|
|
const [profileModalIsOpen, setProfileModalIsOpen] = useState(false);
|
|
const [user] = useUser();
|
|
const toast = useToast();
|
|
const { mutate } = useSWRConfig();
|
|
|
|
useEffect(() => {
|
|
mutate(`/api/teams/${props.team!.id}`);
|
|
}, [props.team]);
|
|
|
|
const leaveTeam = async () => {
|
|
if (!window.confirm(t`Leave the team?`)) {
|
|
return;
|
|
}
|
|
|
|
setSending(true);
|
|
|
|
const success = await sendData("POST", "/api/teams/leave");
|
|
setSending(false);
|
|
if (!success) return;
|
|
|
|
mutate(`/api/teams/${props.team!.id}`);
|
|
|
|
toast(getToastOptions(t`Left the team`, "success"));
|
|
};
|
|
|
|
const teamXPData = getTeamXPInfo(team.roster);
|
|
|
|
return (
|
|
<>
|
|
<MyHead title={team.name ? team.name : ""} />
|
|
{profileModalIsOpen && (
|
|
<TeamProfileModal
|
|
team={team}
|
|
closeModal={() => setProfileModalIsOpen(false)}
|
|
/>
|
|
)}
|
|
<Flex align="center" justify="center">
|
|
{team.twitterName && (
|
|
<TwitterAvatar twitterName={team.twitterName} size="lg" mr={2} />
|
|
)}
|
|
<Heading textAlign="center">{team.name}</Heading>
|
|
|
|
{team.twitterName && (
|
|
<a href={`https://twitter.com/${team.twitterName}`}>
|
|
<IconButton
|
|
aria-label="Link to Twitter"
|
|
icon={<FaTwitter />}
|
|
color="#1DA1F2"
|
|
isRound
|
|
variant="ghost"
|
|
size="sm"
|
|
ml={1}
|
|
/>
|
|
</a>
|
|
)}
|
|
</Flex>
|
|
|
|
{teamXPData.teamXP !== "2000" && (
|
|
<Popover trigger="hover" variant="responsive">
|
|
<PopoverTrigger>
|
|
<Center>
|
|
<Image
|
|
src={`/layout/xsearch.png`}
|
|
height={24}
|
|
width={24}
|
|
alt="X Power icon"
|
|
/>
|
|
<SubText ml={1}>{teamXPData.teamXP}</SubText>
|
|
</Center>
|
|
</PopoverTrigger>
|
|
<PopoverContent bg={CSSVariables.secondaryBgColor} p={6}>
|
|
<PopoverArrow bg={CSSVariables.secondaryBgColor} />
|
|
{teamXPData.placements.map((placement, i) => (
|
|
<Fragment key={placement.discordId}>
|
|
<Flex align="center" justify="center">
|
|
<UserAvatar user={placement} isSmall />
|
|
<Box ml={1}>
|
|
{placement.username}#{placement.discriminator}
|
|
</Box>
|
|
</Flex>
|
|
<Flex align="center" justify="space-evenly" mt={4}>
|
|
<WeaponImage name={placement.weapon} size={32} />
|
|
|
|
<Flex align="center" justify="center">
|
|
<Image
|
|
src={`/layout/xsearch.png`}
|
|
height={24}
|
|
width={24}
|
|
alt="X Power logo"
|
|
/>
|
|
<Box ml={1} fontSize="sm" fontWeight="bold">
|
|
{placement.xPower}
|
|
</Box>
|
|
</Flex>
|
|
</Flex>
|
|
{i !== teamXPData.placements.length - 1 && <Divider my={6} />}
|
|
</Fragment>
|
|
))}
|
|
</PopoverContent>
|
|
</Popover>
|
|
)}
|
|
{user?.id === team.captainId && (
|
|
<Center mt={6} mb={8}>
|
|
<Stack direction={["column", "row"]} spacing={4}>
|
|
<TeamManagementModal team={team} />
|
|
<Button
|
|
leftIcon={<FiEdit />}
|
|
onClick={() => setProfileModalIsOpen(true)}
|
|
size="sm"
|
|
variant="outline"
|
|
>
|
|
<Trans>Edit team profile</Trans>
|
|
</Button>
|
|
</Stack>
|
|
</Center>
|
|
)}
|
|
{user &&
|
|
user.id !== team.captainId &&
|
|
team.roster!.some((teamMember) => user.id === teamMember.id) && (
|
|
<Center my={4}>
|
|
<Button
|
|
variant="outline"
|
|
colorScheme="red"
|
|
onClick={leaveTeam}
|
|
isLoading={sending}
|
|
size="sm"
|
|
>
|
|
<Trans>Leave team</Trans>
|
|
</Button>
|
|
</Center>
|
|
)}
|
|
<Grid
|
|
templateColumns={["0.3fr 3fr", "0.3fr 2fr 3fr"]}
|
|
gridRowGap={4}
|
|
gridColumnGap={2}
|
|
maxW="35rem"
|
|
mx="auto"
|
|
mt={4}
|
|
mb={6}
|
|
>
|
|
{team
|
|
.roster!.sort(
|
|
(a, b) =>
|
|
Number(b.id === team.captainId) - Number(a.id === team.captainId)
|
|
)
|
|
.map((user) => (
|
|
<RosterPlayerBar key={user.id} user={user} />
|
|
))}
|
|
</Grid>
|
|
|
|
{(team.bio || team.recruitingPost) && (
|
|
<Divider my={4} maxW="75ch" mx="auto" />
|
|
)}
|
|
{team.bio && (
|
|
<Box maxW="75ch" mx="auto">
|
|
<Markdown value={team.bio} smallHeaders />
|
|
</Box>
|
|
)}
|
|
{team.recruitingPost && (
|
|
<SubTextCollapse
|
|
title={t`Recruiting post`}
|
|
mt={4}
|
|
maxW="75ch"
|
|
mx="auto"
|
|
>
|
|
<Markdown value={team.recruitingPost} smallHeaders />
|
|
</SubTextCollapse>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const getStaticPaths: GetStaticPaths = async () => {
|
|
return {
|
|
paths: [],
|
|
fallback: "blocking",
|
|
};
|
|
};
|
|
|
|
export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
|
const team = await getTeam({ nameForUrl: params!.team as string });
|
|
|
|
if (!team) return { notFound: true };
|
|
|
|
return {
|
|
props: {
|
|
team,
|
|
},
|
|
revalidate: 1,
|
|
};
|
|
};
|
|
|
|
export default TeamPage;
|