badge redesign
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { Box, Text, Popover, Card } from "@mantine/core";
|
||||
import { Box, Text, Popover, Progress } from "@mantine/core";
|
||||
import { usePlayerBadges, useAllBadges } from "../queries";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { Badge, BadgeProgress } from "../types";
|
||||
import { useMemo } from "react";
|
||||
import { MedalIcon, LockKeyIcon } from "@phosphor-icons/react";
|
||||
|
||||
interface BadgeShowcaseProps {
|
||||
playerId: string;
|
||||
@@ -86,87 +87,189 @@ const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px="md" mb="lg">
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
p={0}
|
||||
<Box mb="lg">
|
||||
<Box mb="sm" px="md">
|
||||
<Text size="sm" fw={600} tt="uppercase" c="dimmed" style={{ letterSpacing: '0.5px' }}>
|
||||
Badges
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
px="md"
|
||||
style={{
|
||||
maxHeight: '200px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
p="md"
|
||||
style={{
|
||||
background: 'light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6))',
|
||||
borderBottom: '1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4))'
|
||||
}}
|
||||
>
|
||||
<Text size="sm" fw={600} tt="uppercase" c="dimmed" style={{ letterSpacing: '0.5px' }}>
|
||||
Badges
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
p="md"
|
||||
mah={120}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(110px, 1fr))',
|
||||
gap: 'var(--mantine-spacing-sm)',
|
||||
overflow: 'scroll',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(85px, 1fr))',
|
||||
gap: 'var(--mantine-spacing-md)',
|
||||
paddingBottom: 'var(--mantine-spacing-sm)',
|
||||
}}
|
||||
>
|
||||
{badgesToDisplay.map((display) => (
|
||||
<Popover key={display.badge.id} width={220} position="bottom" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Card
|
||||
withBorder
|
||||
padding="sm"
|
||||
radius="md"
|
||||
shadow={display.earned ? "xs" : undefined}
|
||||
style={(theme) => ({
|
||||
opacity: display.earned ? 1 : 0.35,
|
||||
cursor: "pointer",
|
||||
transition: 'all 0.2s ease',
|
||||
minHeight: 70,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderStyle: display.earned ? 'solid' : 'dashed',
|
||||
':hover': {
|
||||
transform: display.earned ? 'translateY(-2px)' : 'none',
|
||||
boxShadow: display.earned ? theme.shadows.sm : undefined,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<Text
|
||||
size="xs"
|
||||
ta="center"
|
||||
fw={display.earned ? 600 : 500}
|
||||
c={display.earned ? undefined : "dimmed"}
|
||||
style={{ lineHeight: 1.3 }}
|
||||
{badgesToDisplay.map((display) => {
|
||||
const isStackableBadge = display.badge.criteria?.tournament_wins !== undefined ||
|
||||
display.badge.criteria?.placement !== undefined;
|
||||
const stackCount = display.earned && isStackableBadge
|
||||
? Math.floor((display.progress?.progress || 0) / getTargetProgress(display.badge))
|
||||
: 1;
|
||||
const showStack = stackCount > 1;
|
||||
|
||||
return (
|
||||
<Popover key={display.badge.id} width={220} position="top" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Box
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
transition: 'all 0.2s ease',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{display.badge.name}
|
||||
</Text>
|
||||
</Card>
|
||||
</Popover.Target>
|
||||
{showStack && (
|
||||
<>
|
||||
{[...Array(Math.min(stackCount - 1, 2))].map((_, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
style={(theme) => ({
|
||||
aspectRatio: '1',
|
||||
borderRadius: '12px',
|
||||
background: `linear-gradient(135deg, ${theme.colors[theme.primaryColor][5]} 0%, ${theme.colors[theme.primaryColor][7]} 100%)`,
|
||||
border: `2px solid ${theme.colors[theme.primaryColor][6]}`,
|
||||
position: 'absolute',
|
||||
top: `${(i + 1) * 3}px`,
|
||||
left: `${(i + 1) * 3}px`,
|
||||
right: `-${(i + 1) * 3}px`,
|
||||
bottom: `-${(i + 1) * 3}px`,
|
||||
opacity: 0.6 - (i * 0.2),
|
||||
zIndex: -(i + 1),
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box
|
||||
style={(theme) => ({
|
||||
aspectRatio: '1',
|
||||
borderRadius: '12px',
|
||||
background: display.earned
|
||||
? `linear-gradient(135deg, ${theme.colors[theme.primaryColor][5]} 0%, ${theme.colors[theme.primaryColor][7]} 100%)`
|
||||
: 'light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6))',
|
||||
border: `2px solid ${display.earned ? theme.colors[theme.primaryColor][6] : 'light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4))'}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
padding: 'var(--mantine-spacing-xs)',
|
||||
position: 'relative',
|
||||
boxShadow: display.earned
|
||||
? `0 2px 8px ${theme.colors[theme.primaryColor][6]}30`
|
||||
: '0 1px 3px rgba(0, 0, 0, 0.1)',
|
||||
opacity: display.earned ? 1 : 0.5,
|
||||
zIndex: 1,
|
||||
':hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: display.earned
|
||||
? `0 4px 12px ${theme.colors[theme.primaryColor][6]}40`
|
||||
: '0 2px 6px rgba(0, 0, 0, 0.15)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
{display.earned ? (
|
||||
<MedalIcon
|
||||
size={32}
|
||||
weight="fill"
|
||||
style={{
|
||||
color: 'white',
|
||||
filter: 'drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LockKeyIcon
|
||||
size={28}
|
||||
weight="regular"
|
||||
style={{
|
||||
color: 'var(--mantine-color-dimmed)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showStack && (
|
||||
<Box
|
||||
style={(theme) => ({
|
||||
position: 'absolute',
|
||||
top: '4px',
|
||||
right: '4px',
|
||||
background: theme.colors[theme.primaryColor][8],
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '10px',
|
||||
fontWeight: 700,
|
||||
border: '2px solid white',
|
||||
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
||||
})}
|
||||
>
|
||||
{stackCount}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Text
|
||||
size="xs"
|
||||
fw={display.earned ? 600 : 500}
|
||||
ta="center"
|
||||
c={display.earned ? 'white' : 'dimmed'}
|
||||
style={{
|
||||
lineHeight: 1.2,
|
||||
textShadow: display.earned ? '0 1px 2px rgba(0, 0, 0, 0.3)' : 'none',
|
||||
}}
|
||||
>
|
||||
{display.badge.name}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Box>
|
||||
<Text size="xs" fw={600} mb={4}>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{display.badge.name}
|
||||
</Text>
|
||||
<Text size="xs" mb={4}>
|
||||
<Text size="xs" c="dimmed" mb={isCurrentUser ? "sm" : undefined}>
|
||||
{display.badge.description}
|
||||
</Text>
|
||||
{isCurrentUser && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Progress: {display.progressText}
|
||||
</Text>
|
||||
<Box>
|
||||
<Box mb="xs" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text size="xs" fw={500} c="dimmed">
|
||||
Progress
|
||||
</Text>
|
||||
<Text size="xs" fw={600} c="dimmed">
|
||||
{display.progressText}
|
||||
</Text>
|
||||
</Box>
|
||||
<Progress
|
||||
value={(display.progress?.progress || 0) / getTargetProgress(display.badge) * 100}
|
||||
size="sm"
|
||||
radius="sm"
|
||||
color={display.earned ? "green" : undefined}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user