redesign h2h player picker: single slot + Me quick-select
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { Stack, Text, TextInput, Box, Paper, Group, Divider, Center, ActionIcon, Badge } from "@mantine/core";
|
||||
import { Stack, Text, TextInput, Box, Paper, Group, Divider, ActionIcon, Button } from "@mantine/core";
|
||||
import { useState, useMemo } from "react";
|
||||
import { MagnifyingGlassIcon, XIcon, ArrowRightIcon } from "@phosphor-icons/react";
|
||||
import { MagnifyingGlassIcon, XIcon, ArrowRightIcon, PlusIcon } from "@phosphor-icons/react";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { useAllPlayerStats } from "../queries";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import PlayerHeadToHeadSheet from "./player-head-to-head-sheet";
|
||||
@@ -10,16 +11,24 @@ import PlayerAvatar from "@/components/player-avatar";
|
||||
|
||||
const LeagueHeadToHead = () => {
|
||||
const { t } = useLingui();
|
||||
const { user } = useAuth();
|
||||
const [player1Id, setPlayer1Id] = useState<string | null>(null);
|
||||
const [player2Id, setPlayer2Id] = useState<string | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const { data: allPlayerStats } = useAllPlayerStats();
|
||||
const h2hSheet = useSheet();
|
||||
|
||||
const myName = useMemo(() => {
|
||||
if (!user) return "";
|
||||
const fromStats = allPlayerStats?.find((p) => p.player_id === user.id)?.player_name;
|
||||
return fromStats || [user.first_name, user.last_name].filter(Boolean).join(" ");
|
||||
}, [user, allPlayerStats]);
|
||||
|
||||
const player1Name = useMemo(() => {
|
||||
if (!player1Id || !allPlayerStats) return "";
|
||||
return allPlayerStats.find((p) => p.player_id === player1Id)?.player_name || "";
|
||||
}, [player1Id, allPlayerStats]);
|
||||
if (!player1Id) return "";
|
||||
if (user && player1Id === user.id) return myName;
|
||||
return allPlayerStats?.find((p) => p.player_id === player1Id)?.player_name || "";
|
||||
}, [player1Id, allPlayerStats, user, myName]);
|
||||
|
||||
const player2Name = useMemo(() => {
|
||||
if (!player2Id || !allPlayerStats) return "";
|
||||
@@ -30,21 +39,17 @@ const LeagueHeadToHead = () => {
|
||||
if (!allPlayerStats) return [];
|
||||
|
||||
return allPlayerStats
|
||||
.filter((stat) => {
|
||||
if (player1Id && stat.player_id === player1Id) return false;
|
||||
if (player2Id && stat.player_id === player2Id) return false;
|
||||
return true;
|
||||
})
|
||||
.filter((stat) => stat.player_id !== player1Id)
|
||||
.filter((stat) =>
|
||||
stat.player_name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
.sort((a, b) => b.matches - a.matches);
|
||||
}, [allPlayerStats, player1Id, player2Id, search]);
|
||||
}, [allPlayerStats, player1Id, search]);
|
||||
|
||||
const handlePlayerClick = (playerId: string) => {
|
||||
if (!player1Id) {
|
||||
setPlayer1Id(playerId);
|
||||
} else if (!player2Id) {
|
||||
} else {
|
||||
setPlayer2Id(playerId);
|
||||
h2hSheet.open();
|
||||
}
|
||||
@@ -52,160 +57,73 @@ const LeagueHeadToHead = () => {
|
||||
|
||||
const handleClearPlayer1 = () => {
|
||||
setPlayer1Id(null);
|
||||
if (player2Id) {
|
||||
setPlayer1Id(player2Id);
|
||||
setPlayer2Id(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearPlayer2 = () => {
|
||||
setPlayer2Id(null);
|
||||
};
|
||||
|
||||
const activeStep = !player1Id ? 1 : !player2Id ? 2 : 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap="md">
|
||||
<Paper px="md" pt="md" pb="sm" withBorder shadow="sm">
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Paper
|
||||
p="sm"
|
||||
withBorder
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 70,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
position: "relative",
|
||||
borderWidth: 2,
|
||||
borderColor: activeStep === 1 ? "var(--mantine-primary-color-filled)" : undefined,
|
||||
backgroundColor: player1Id && activeStep !== 1 ? "var(--mantine-color-default-hover)" : undefined,
|
||||
cursor: player1Id && activeStep === 0 ? "pointer" : undefined,
|
||||
transition: "all 150ms ease",
|
||||
}}
|
||||
onClick={player1Id && activeStep === 0 ? handleClearPlayer1 : undefined}
|
||||
>
|
||||
{player1Id ? (
|
||||
<>
|
||||
<Stack gap={4} align="center" style={{ flex: 1 }}>
|
||||
<PlayerAvatar name={player1Name} size={36} disableFullscreen />
|
||||
<Text size="xs" fw={600} ta="center" lineClamp={1}>
|
||||
{player1Name}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="gray"
|
||||
size="xs"
|
||||
radius="xl"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClearPlayer1();
|
||||
}}
|
||||
style={{ position: "absolute", top: 4, right: 4 }}
|
||||
>
|
||||
<XIcon size={10} />
|
||||
</ActionIcon>
|
||||
</>
|
||||
) : (
|
||||
<Stack gap={4} align="center">
|
||||
<PlayerAvatar size={36} disableFullscreen />
|
||||
<Text size="xs" c="dimmed" fw={500}>
|
||||
<Trans>Player 1</Trans>
|
||||
<Box px="md" pt="md">
|
||||
<Paper
|
||||
p="sm"
|
||||
withBorder
|
||||
shadow="sm"
|
||||
style={{
|
||||
minHeight: 64,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderWidth: 2,
|
||||
borderColor: !player1Id ? "var(--mantine-primary-color-filled)" : undefined,
|
||||
transition: "border-color 150ms ease",
|
||||
}}
|
||||
>
|
||||
{player1Id ? (
|
||||
<Group justify="space-between" wrap="nowrap" style={{ flex: 1 }}>
|
||||
<Group gap="sm" wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
<PlayerAvatar name={player1Name} size={36} disableFullscreen />
|
||||
<Box style={{ minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} truncate>
|
||||
{player1Name}
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
<Center>
|
||||
<Text size="xl" fw={700} c="dimmed">
|
||||
<Trans>VS</Trans>
|
||||
</Text>
|
||||
</Center>
|
||||
|
||||
<Paper
|
||||
p="sm"
|
||||
withBorder
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 70,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
position: "relative",
|
||||
borderWidth: 2,
|
||||
borderColor: activeStep === 2 ? "var(--mantine-primary-color-filled)" : undefined,
|
||||
backgroundColor: player2Id && activeStep !== 2 ? "var(--mantine-color-default-hover)" : undefined,
|
||||
cursor: player2Id && activeStep === 0 ? "pointer" : undefined,
|
||||
transition: "all 150ms ease",
|
||||
}}
|
||||
onClick={player2Id && activeStep === 0 ? handleClearPlayer2 : undefined}
|
||||
>
|
||||
{player2Id ? (
|
||||
<>
|
||||
<Stack gap={4} align="center" style={{ flex: 1 }}>
|
||||
<PlayerAvatar name={player2Name} size={36} disableFullscreen />
|
||||
<Text size="xs" fw={600} ta="center" lineClamp={1}>
|
||||
{player2Name}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="gray"
|
||||
size="xs"
|
||||
radius="xl"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClearPlayer2();
|
||||
}}
|
||||
style={{ position: "absolute", top: 4, right: 4 }}
|
||||
>
|
||||
<XIcon size={10} />
|
||||
</ActionIcon>
|
||||
</>
|
||||
) : (
|
||||
<Stack gap={4} align="center">
|
||||
<PlayerAvatar size={36} disableFullscreen />
|
||||
<Text size="xs" c="dimmed" fw={500}>
|
||||
<Trans>Player 2</Trans>
|
||||
<Text size="xs" c="dimmed">
|
||||
<Trans>Tap an opponent below to compare</Trans>
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
</Group>
|
||||
|
||||
{activeStep > 0 ? (
|
||||
<Badge
|
||||
variant="light"
|
||||
size="sm"
|
||||
radius="sm"
|
||||
fullWidth
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
>
|
||||
{activeStep === 1 && <Trans>Select first player</Trans>}
|
||||
{activeStep === 2 && <Trans>Select second player</Trans>}
|
||||
</Badge>
|
||||
) : (
|
||||
<Group justify="center">
|
||||
<Text
|
||||
size="xs"
|
||||
c="dimmed"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
setPlayer1Id(null);
|
||||
setPlayer2Id(null);
|
||||
}}
|
||||
td="underline"
|
||||
</Box>
|
||||
</Group>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="gray"
|
||||
size="sm"
|
||||
radius="xl"
|
||||
onClick={handleClearPlayer1}
|
||||
aria-label={t`Clear selected player`}
|
||||
>
|
||||
<Trans>Clear both players</Trans>
|
||||
</Text>
|
||||
<XIcon size={12} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
) : (
|
||||
<Group justify="space-between" wrap="nowrap" style={{ flex: 1 }}>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<PlayerAvatar size={36} disableFullscreen />
|
||||
<Text size="sm" c="dimmed" fw={500}>
|
||||
<Trans>Select a player</Trans>
|
||||
</Text>
|
||||
</Group>
|
||||
{user && (
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
radius="xl"
|
||||
leftSection={<PlusIcon size={12} />}
|
||||
onClick={() => setPlayer1Id(user.id)}
|
||||
>
|
||||
<Trans>Me</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<TextInput
|
||||
placeholder={t`Search players`}
|
||||
@@ -230,6 +148,7 @@ const LeagueHeadToHead = () => {
|
||||
p="md"
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
className="flxn-press"
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
transition: "background-color 150ms ease",
|
||||
@@ -250,7 +169,7 @@ const LeagueHeadToHead = () => {
|
||||
</Text>
|
||||
</Box>
|
||||
<ActionIcon variant="subtle" color="gray" size="lg" radius="xl">
|
||||
<ArrowRightIcon size={18} />
|
||||
{player1Id ? <ArrowRightIcon size={18} /> : <PlusIcon size={18} />}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
{index < filteredPlayers.length - 1 && <Divider />}
|
||||
|
||||
Reference in New Issue
Block a user