Files
flxn-app/src/features/badges/components/badge-showcase.tsx
2025-10-01 13:26:42 -05:00

190 lines
5.2 KiB
TypeScript

import { Box, Text, Tooltip, Card } from "@mantine/core";
import { usePlayerBadges, useAllBadges } from "../queries";
import { useAuth } from "@/contexts/auth-context";
import { Badge, BadgeProgress } from "../types";
import { useMemo } from "react";
interface BadgeShowcaseProps {
playerId: string;
}
interface BadgeDisplay {
badge: Badge;
progress?: BadgeProgress;
earned: boolean;
progressText: string;
}
const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => {
const { user } = useAuth();
const { data: badgeProgress } = usePlayerBadges(playerId);
const { data: allBadges } = useAllBadges();
const isCurrentUser = user?.id === playerId;
const badgesToDisplay = useMemo(() => {
const displays: BadgeDisplay[] = [];
if (isCurrentUser) {
for (const badge of allBadges) {
const progress = badgeProgress.find(bp => bp.badge.id === badge.id);
const earned = progress?.earned || false;
if (badge.type === 'manual' && !earned) {
continue;
}
let progressText = "";
if (progress) {
const target = getTargetProgress(badge);
progressText = `${progress.progress} / ${target}`;
} else {
const target = getTargetProgress(badge);
progressText = `0 / ${target}`;
}
displays.push({
badge,
progress,
earned,
progressText,
});
}
displays.sort((a, b) => {
if (a.earned && !b.earned) return -1;
if (!a.earned && b.earned) return 1;
return a.badge.order - b.badge.order;
});
} else {
const earnedProgress = badgeProgress.filter(bp => bp.earned);
for (const progress of earnedProgress) {
const badge: Badge = {
...progress.badge,
criteria: {},
created: progress.created,
updated: progress.updated,
};
const target = getTargetProgress(badge);
displays.push({
badge,
progress,
earned: true,
progressText: `${progress.progress} / ${target}`,
});
}
displays.sort((a, b) => a.badge.order - b.badge.order);
}
return displays;
}, [allBadges, badgeProgress, isCurrentUser]);
if (badgesToDisplay.length === 0) {
return null;
}
return (
<Box mb="lg">
<Card
withBorder
radius="md"
p={0}
>
<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',
}}
>
{badgesToDisplay.map((display) => (
<Tooltip
key={display.badge.id}
label={
<Box>
<Text size="xs" fw={600} mb={4}>
{display.badge.name}
</Text>
<Text size="xs" mb={4}>
{display.badge.description}
</Text>
{isCurrentUser && (
<Text size="xs" c="dimmed">
Progress: {display.progressText}
</Text>
)}
</Box>
}
multiline
w={220}
>
<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 }}
>
{display.badge.name}
</Text>
</Card>
</Tooltip>
))}
</Box>
</Card>
</Box>
);
};
function getTargetProgress(badge: Badge): number {
const criteria = badge.criteria;
return (
criteria.matches_played ||
criteria.tournament_wins ||
criteria.tournaments_attended ||
criteria.overtime_matches ||
criteria.overtime_wins ||
criteria.consecutive_wins ||
1
);
}
export default BadgeShowcase;