mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 01:09:02 -05:00
250 lines
7.4 KiB
TypeScript
250 lines
7.4 KiB
TypeScript
import {
|
|
Box,
|
|
BoxProps,
|
|
Flex,
|
|
IconButton,
|
|
Popover,
|
|
PopoverBody,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@chakra-ui/react";
|
|
import { Plural, t, Trans } from "@lingui/macro";
|
|
import { Ability, Prisma } from "@prisma/client";
|
|
import Flag from "components/common/Flag";
|
|
import ModeImage from "components/common/ModeImage";
|
|
import MyIconButton from "components/common/MyIconButton";
|
|
import UserAvatar from "components/common/UserAvatar";
|
|
import WeaponImage from "components/common/WeaponImage";
|
|
import { CSSVariables } from "utils/CSSVariables";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useState } from "react";
|
|
import { FiBarChart2, FiEdit, FiInfo, FiTarget } from "react-icons/fi";
|
|
import { PartialBy } from "utils/types";
|
|
import Gears from "./Gears";
|
|
import ViewAP from "./ViewAP";
|
|
import ViewSlots from "./ViewSlots";
|
|
|
|
interface BuildCardProps {
|
|
// TODO: don't select unnecessary fields
|
|
build: PartialBy<Prisma.BuildGetPayload<{ include: { user: true } }>, "user">;
|
|
otherBuildCount?: number;
|
|
onShowAllByUser?: () => void;
|
|
onEdit?: (build: BuildCardProps["build"]) => void;
|
|
showWeapon?: boolean;
|
|
}
|
|
|
|
const BuildCard: React.FC<BuildCardProps & BoxProps> = ({
|
|
build,
|
|
onEdit,
|
|
otherBuildCount,
|
|
onShowAllByUser,
|
|
showWeapon,
|
|
...props
|
|
}) => {
|
|
const [apView, setApView] = useState(false);
|
|
|
|
const username = build.user?.username;
|
|
|
|
return (
|
|
<>
|
|
<Box
|
|
w="300px"
|
|
rounded="lg"
|
|
overflow="hidden"
|
|
boxShadow="md"
|
|
bg={CSSVariables.secondaryBgColor}
|
|
p="20px"
|
|
{...props}
|
|
>
|
|
<Box display="flex" flexDirection="column" h="100%">
|
|
{build.title && (
|
|
<Box fontWeight="semibold" as="h4" lineHeight="tight" mt="0.3em">
|
|
{build.title}
|
|
</Box>
|
|
)}
|
|
{build.user && (
|
|
<Box
|
|
my={2}
|
|
textOverflow="ellipsis"
|
|
color={CSSVariables.themeGray}
|
|
fontWeight="semibold"
|
|
letterSpacing="wide"
|
|
fontSize="sm"
|
|
whiteSpace="nowrap"
|
|
overflow="hidden"
|
|
title={`${build.user.username}#${build.user.discriminator}`}
|
|
>
|
|
<Link href={`/u/${build.user.discordId}`} prefetch={false}>
|
|
<a>
|
|
<Flex alignItems="center">
|
|
<UserAvatar user={build.user} isSmall mr={2} />
|
|
{build.user.username}#{build.user.discriminator}
|
|
</Flex>
|
|
</a>
|
|
</Link>
|
|
</Box>
|
|
)}
|
|
{showWeapon && (
|
|
<Box>
|
|
<WeaponImage name={build.weapon} size={64} />
|
|
</Box>
|
|
)}
|
|
<Flex alignItems="center">
|
|
<Box
|
|
color={CSSVariables.themeGray}
|
|
fontWeight="semibold"
|
|
letterSpacing="wide"
|
|
fontSize="xs"
|
|
title={build.updatedAt.toLocaleString()}
|
|
mr={2}
|
|
>
|
|
{build.updatedAt.toLocaleDateString()}
|
|
</Box>
|
|
{build.jpn ? (
|
|
<Flag countryCode="JP" />
|
|
) : build.top500 ? (
|
|
<Image
|
|
src={`/layout/xsearch.png`}
|
|
height={24}
|
|
width={24}
|
|
title={t`Maker of the build has finished in the top 500 of X Rank with this weapon`}
|
|
alt={t`Maker of the build has finished in the top 500 of X Rank with this weapon`}
|
|
/>
|
|
) : null}
|
|
</Flex>
|
|
<Flex mt="0.3em">
|
|
<MyIconButton
|
|
variant="ghost"
|
|
onClick={() => setApView(!apView)}
|
|
popup={apView ? t`Show abilities` : t`Show ability points`}
|
|
icon={<FiTarget />}
|
|
fontSize="20px"
|
|
mr="0.5em"
|
|
/>
|
|
<Link
|
|
href={encodeURI(
|
|
`/analyzer?weapon=${build.weapon}&head=${build.headAbilities}&clothing=${build.clothingAbilities}&shoes=${build.shoesAbilities}`
|
|
)}
|
|
>
|
|
<a>
|
|
<MyIconButton
|
|
variant="ghost"
|
|
popup={t`Show in Build Analyzer`}
|
|
fontSize="20px"
|
|
icon={<FiBarChart2 />}
|
|
mr="0.5em"
|
|
/>
|
|
</a>
|
|
</Link>
|
|
<Description />
|
|
{onEdit && (
|
|
<MyIconButton
|
|
popup={t`Edit build`}
|
|
onClick={() => onEdit(build)}
|
|
icon={<FiEdit />}
|
|
fontSize="20px"
|
|
ml="0.5em"
|
|
/>
|
|
)}
|
|
</Flex>
|
|
<Box mt="1em">
|
|
<Gears build={build} />
|
|
</Box>
|
|
<Box
|
|
display="flex"
|
|
flexDirection="column"
|
|
flexGrow={1}
|
|
justifyContent="center"
|
|
mt="1em"
|
|
>
|
|
{apView ? (
|
|
<ViewAP
|
|
aps={build.abilityPoints as Record<Ability, number>}
|
|
extraAps={
|
|
build.clothingAbilities[0] === "AD"
|
|
? build.clothingAbilities
|
|
.slice(1)
|
|
.reduce(
|
|
(acc: Partial<Record<Ability, number>>, ability) => {
|
|
if (ability in acc) acc[ability]! += 3;
|
|
else acc[ability] = 3;
|
|
return acc;
|
|
},
|
|
{}
|
|
)
|
|
: undefined
|
|
}
|
|
/>
|
|
) : (
|
|
<ViewSlots abilities={build} />
|
|
)}
|
|
</Box>
|
|
{otherBuildCount ? (
|
|
<Box
|
|
display="flex"
|
|
justifyContent="space-between"
|
|
alignItems="center"
|
|
mt="1em"
|
|
>
|
|
<Box
|
|
mx="auto"
|
|
fontSize="0.8em"
|
|
color={CSSVariables.themeColor}
|
|
textAlign="center"
|
|
onClick={onShowAllByUser}
|
|
cursor="pointer"
|
|
_hover={{ textDecoration: "underline" }}
|
|
>
|
|
<Plural
|
|
value={otherBuildCount}
|
|
one={<Trans>Show # more build by {username}</Trans>}
|
|
other={<Trans>Show # more builds by {username}</Trans>}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
) : null}
|
|
</Box>
|
|
</Box>
|
|
</>
|
|
);
|
|
|
|
function Description() {
|
|
if (build.modes.length === 0 && !build.description) return null;
|
|
|
|
return (
|
|
<Popover placement="top" trigger="hover">
|
|
<PopoverTrigger>
|
|
<IconButton
|
|
variant="ghost"
|
|
isRound
|
|
aria-label="Show description"
|
|
fontSize="20px"
|
|
icon={<FiInfo />}
|
|
/>
|
|
</PopoverTrigger>
|
|
<PopoverContent
|
|
zIndex={4}
|
|
width="220px"
|
|
backgroundColor={CSSVariables.secondaryBgColor}
|
|
>
|
|
<PopoverBody whiteSpace="pre-wrap">
|
|
<Box>
|
|
<Box>
|
|
{build.modes.map((mode) => (
|
|
<Box key={mode} as="span" mr={1}>
|
|
<ModeImage mode={mode} size={24} />
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
{build.description}
|
|
</Box>
|
|
</PopoverBody>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
}
|
|
};
|
|
|
|
export default BuildCard;
|