225 lines
7.3 KiB
TypeScript
225 lines
7.3 KiB
TypeScript
import { Text, Group, Stack, Paper, Indicator, Box, Tooltip, ActionIcon } from "@mantine/core";
|
|
import HeadToHeadIcon from "@/components/head-to-head-icon";
|
|
import { useNavigate } from "@tanstack/react-router";
|
|
import { Match } from "../types";
|
|
import TeamAvatar from "@/components/team-avatar";
|
|
import EmojiBar from "@/features/reactions/components/emoji-bar";
|
|
import { memo, Suspense } from "react";
|
|
import { useSheet } from "@/hooks/use-sheet";
|
|
import Sheet from "@/components/sheet/sheet";
|
|
import TeamHeadToHeadSheet from "./team-head-to-head-sheet";
|
|
import AnimatedScore from "./animated-score";
|
|
import { Trans, useLingui } from "@lingui/react/macro";
|
|
import { track } from "@/lib/telemetry/client";
|
|
|
|
interface MatchCardProps {
|
|
match: Match;
|
|
hideH2H?: boolean;
|
|
}
|
|
|
|
const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
|
|
const navigate = useNavigate();
|
|
const h2hSheet = useSheet();
|
|
const { t } = useLingui();
|
|
const isHomeWin = match.home_cups > match.away_cups;
|
|
const isAwayWin = match.away_cups > match.home_cups;
|
|
const isStarted = match.status === "started";
|
|
const hasPrivate = match.home?.private || match.away?.private;
|
|
|
|
const handleHomeTeamClick = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
if (match.home?.id) {
|
|
navigate({ to: `/teams/${match.home.id}` });
|
|
}
|
|
};
|
|
|
|
const handleAwayTeamClick = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
if (match.away?.id) {
|
|
navigate({ to: `/teams/${match.away.id}` });
|
|
}
|
|
};
|
|
|
|
const handleH2HClick = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
track("h2h_viewed", {
|
|
source: "match_card",
|
|
context: [match.home?.name, match.away?.name].filter(Boolean).join(" v "),
|
|
});
|
|
h2hSheet.open();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Box style={{ position: "relative" }}>
|
|
<Paper
|
|
px="md"
|
|
py="md"
|
|
withBorder
|
|
radius="md"
|
|
className="flxn-match-card"
|
|
style={{ position: "relative", zIndex: 2 }}
|
|
>
|
|
<Stack gap="sm">
|
|
<Group gap="xs" justify="space-between">
|
|
<Group gap="xs" style={{ flex: 1 }}>
|
|
{isStarted && (
|
|
<Indicator
|
|
size={8}
|
|
color="red"
|
|
processing
|
|
position="middle-start"
|
|
offset={0}
|
|
/>
|
|
)}
|
|
<Text size="xs" fw={600} lineClamp={1} c="dimmed">
|
|
{match.tournament.name}
|
|
</Text>
|
|
{!match.tournament.regional && (
|
|
<>
|
|
<Text c="dimmed">-</Text>
|
|
<Text size="xs" c="dimmed">
|
|
<Trans>Round {match.round + 1}</Trans>
|
|
{match.is_losers_bracket && <Trans> (Losers)</Trans>}
|
|
</Text>
|
|
</>
|
|
)}
|
|
</Group>
|
|
{match.home && match.away && !hideH2H && !hasPrivate && (
|
|
<Tooltip label={t`Head to Head`} withArrow position="left">
|
|
<ActionIcon
|
|
variant="subtle"
|
|
size="sm"
|
|
onClick={handleH2HClick}
|
|
aria-label={t`View head-to-head`}
|
|
w={40}
|
|
>
|
|
<HeadToHeadIcon />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
</Group>
|
|
|
|
<Group justify="space-between" align="center">
|
|
<Group gap="sm" style={{ flex: 1 }}>
|
|
<Box
|
|
style={{ cursor: "pointer" }}
|
|
onClick={handleHomeTeamClick}
|
|
>
|
|
<TeamAvatar
|
|
team={match.home!}
|
|
size={40}
|
|
radius="sm"
|
|
winner={isHomeWin}
|
|
isRegional={match.tournament.regional}
|
|
/>
|
|
</Box>
|
|
<Tooltip
|
|
label={match.home?.name!}
|
|
disabled={!match.home?.name}
|
|
events={{ hover: true, focus: true, touch: true }}
|
|
>
|
|
<Text
|
|
size="sm"
|
|
fw={600}
|
|
lineClamp={1}
|
|
style={{ minWidth: 0, flex: 1, cursor: 'pointer' }}
|
|
>
|
|
{match.home?.name!}
|
|
</Text>
|
|
</Tooltip>
|
|
</Group>
|
|
<Stack gap={1}>
|
|
{match.home?.players.map((p) => (
|
|
<Text key={`match-card-p-${p.id}`} size="xs" fw={600} c="dimmed" ta="right">
|
|
{p.first_name} {p.last_name}
|
|
</Text>
|
|
))}
|
|
</Stack>
|
|
<AnimatedScore
|
|
size="xl"
|
|
fw={700}
|
|
c={"dimmed"}
|
|
display={match.status === "ended" ? undefined : "none"}
|
|
value={match.home_cups}
|
|
/>
|
|
</Group>
|
|
|
|
<Group justify="space-between" align="center">
|
|
<Group gap="sm" style={{ flex: 1 }}>
|
|
<Box
|
|
style={{ cursor: "pointer" }}
|
|
onClick={handleAwayTeamClick}
|
|
>
|
|
<TeamAvatar
|
|
team={match.away!}
|
|
size={40}
|
|
radius="sm"
|
|
winner={isAwayWin}
|
|
isRegional={match.tournament.regional}
|
|
/>
|
|
</Box>
|
|
<Tooltip
|
|
label={match.away?.name}
|
|
disabled={!match.away?.name}
|
|
events={{ hover: true, focus: true, touch: true }}
|
|
>
|
|
<Text
|
|
size="sm"
|
|
fw={600}
|
|
lineClamp={1}
|
|
style={{ minWidth: 0, flex: 1, cursor: 'pointer' }}
|
|
>
|
|
{match.away?.name}
|
|
</Text>
|
|
</Tooltip>
|
|
</Group>
|
|
<Stack gap={1}>
|
|
{match.away?.players.map((p) => (
|
|
<Text key={`match-card-p-${p.id}`} size="xs" fw={600} c="dimmed" ta="right">
|
|
{p.first_name} {p.last_name}
|
|
</Text>
|
|
))}
|
|
</Stack>
|
|
<AnimatedScore
|
|
size="xl"
|
|
fw={700}
|
|
c={"dimmed"}
|
|
display={match.status === "ended" ? undefined : "none"}
|
|
value={match.away_cups}
|
|
/>
|
|
</Group>
|
|
</Stack>
|
|
</Paper>
|
|
|
|
<Paper
|
|
px="md"
|
|
pb={4}
|
|
className="flxn-reaction-row"
|
|
style={{
|
|
position: "relative",
|
|
zIndex: 1,
|
|
marginTop: -6,
|
|
paddingTop: 8,
|
|
}}
|
|
>
|
|
<Suspense>
|
|
<EmojiBar matchId={match.id} source="match_card" />
|
|
</Suspense>
|
|
</Paper>
|
|
</Box>
|
|
|
|
{match.home && match.away && !hideH2H && h2hSheet.isOpen && (
|
|
<Sheet
|
|
title={t`Head to Head`}
|
|
{...h2hSheet.props}
|
|
>
|
|
<TeamHeadToHeadSheet team1={match.home} team2={match.away} isOpen={h2hSheet.props.opened} />
|
|
</Sheet>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default memo(MatchCard);
|