name blurbs, skeleton and link

This commit is contained in:
yohlo
2025-10-16 15:39:46 -05:00
parent 4b66a4bd13
commit 15ed78a9c5
5 changed files with 142 additions and 25 deletions

View File

@@ -1,4 +1,5 @@
import BadgeStatsTable from '@/features/badges/components/badge-stats-table';
import BadgeStatsTableSkeleton from '@/features/badges/components/badge-stats-table-skeleton';
import { badgeQueries, useAllBadges } from '@/features/badges/queries';
import PlayerStatsTableSkeleton from '@/features/players/components/player-stats-table-skeleton';
import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch';
@@ -16,15 +17,15 @@ export const Route = createFileRoute('/_authed/badges')({
fullWidth: true,
header: {
title: 'All Badges',
withBackButton: true,
},
refresh: [badgeQueries.allBadges().queryKey],
}),
});
function Badges() {
//TODO: CHANGE FALLBACK
return (
<Suspense fallback={<PlayerStatsTableSkeleton />}>
<Suspense fallback={<BadgeStatsTableSkeleton />}>
<div>
<BadgeStatsTable />
</div>

View File

@@ -0,0 +1,67 @@
import {
Stack,
Container,
Box,
Divider,
Grid,
Skeleton,
} from '@mantine/core';
const BadgeStatsTableSkeleton = () => {
return (
<Container size='100%' px={0}>
<Stack gap='xs'>
<Stack gap={0}>
{Array.from({ length: 15 }).map((_, index) => (
<BadgeStatRowSkeleton
key={`skeleton-${index}`}
isLastRow={index >= 14}
/>
))}
</Stack>
</Stack>
</Container>
);
};
export default BadgeStatsTableSkeleton;
interface BadgeStatRowSkeletonProps {
isLastRow: boolean;
}
const BadgeStatRowSkeleton: React.FC<BadgeStatRowSkeletonProps> = ({
isLastRow,
}) => {
return (
<Box>
<Grid p={'xs'} align='center'>
<Grid.Col span={2} style={{ display: 'flex', alignItems: 'center' }}>
<Skeleton circle height={48} width={48} />
</Grid.Col>
<Grid.Col span={'auto'}>
<Stack gap={8}>
<Skeleton height={16} width='60%' />
<Skeleton height={14} width='80%' />
<Skeleton height={12} width='50%' mt={4} />
</Stack>
</Grid.Col>
<Grid.Col
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
width: '73px',
flexBasis: '73px',
}}
>
<Stack gap={4} align='center'>
<Skeleton height={20} width={40} />
<Skeleton height={12} width={50} />
</Stack>
</Grid.Col>
</Grid>
{!isLastRow && <Divider />}
</Box>
);
};

View File

@@ -15,6 +15,9 @@ import { Badge, EarnedBadge } from '../types';
import { useAllPlayers } from '@/features/teams/hooks/use-available-players';
import { useSheet } from '@/hooks/use-sheet';
import Sheet from '@/components/sheet/sheet';
import PlayerList from '@/features/players/components/player-list';
import { useAuth } from '@/contexts/auth-context';
import { Player } from '@/features/players/types';
const BadgeStatsTable = () => {
const { data: allBadges } = useAllBadges();
@@ -79,16 +82,69 @@ const BadgeStatRow: React.FC<BadgeStatRowProps> = ({
isLastRow,
}) => {
const badgeSheet = useSheet();
const { user } = useAuth();
const playerNamesBlurb = useMemo(() => {
if (earnedBadges.length === 0) return 'No players yet';
const currentUserHasBadge = earnedBadges.some(
(eb) => eb.player.id === user?.id
);
const otherPlayers = earnedBadges.filter(
(eb) => eb.player.id !== user?.id
);
const displayPlayers = currentUserHasBadge
? otherPlayers.slice(0, 2)
: earnedBadges.slice(0, 3);
const names = displayPlayers.map((eb) => eb.player.first_name);
if (currentUserHasBadge) {
const remaining = earnedBadges.length - 1 - names.length;
if (names.length === 0 && remaining === 0) {
return 'You';
} else if (names.length === 0 && remaining > 0) {
return `You and ${remaining} other${remaining > 1 ? 's' : ''}`;
} else if (remaining > 0) {
return `You, ${names.join(', ')} and ${remaining} other${remaining > 1 ? 's' : ''}`;
} else {
return `You${names.length > 0 ? ` and ${names.join(', ')}` : ''}`;
}
} else {
const remaining = earnedBadges.length - names.length;
if (remaining > 0) {
return `${names.join(', ')} and ${remaining} other${remaining > 1 ? 's' : ''}`;
} else {
return names.join(', ');
}
}
}, [earnedBadges, user?.id]);
const playersForList: Player[] = useMemo(() => {
return earnedBadges.map((eb) => ({
id: eb.player.id,
first_name: eb.player.first_name,
last_name: eb.player.last_name,
} as Player));
}, [earnedBadges]);
return (
<Box key={badge.id}>
<UnstyledButton onClick={badgeSheet.open} w='100%'>
<Grid p={'xs'}>
<Grid.Col span={2}>
<Grid p={'xs'} align='center'>
<Grid.Col span={2} style={{ display: 'flex', alignItems: 'center' }}>
<BadgeIcon badge={badge} filled={true} />
</Grid.Col>
<Grid.Col span={'auto'}>
<Text fw={700}>{badge.name}</Text>
<Text fw={500}>{badge.description}</Text>
<Stack gap={2}>
<Text fw={700}>{badge.name}</Text>
<Text size="sm" fw={500}>{badge.description}</Text>
<Text size='xs' c='dimmed' mt={4}>
{playerNamesBlurb}
</Text>
</Stack>
</Grid.Col>
<Grid.Col
style={{
@@ -100,28 +156,20 @@ const BadgeStatRow: React.FC<BadgeStatRowProps> = ({
}}
>
<Stack gap={0} align='center'>
<Text size='lg' fw={700}>
<Text c="dimmed" size='lg' fw={700}>
{(
((earnedBadges?.length ?? 0) / totalNumPlayers) *
100
).toFixed(0)}
%
</Text>
<Text size='xs'>of players</Text>
<Text c="dimmed" size='xs'>of players</Text>
</Stack>
</Grid.Col>
</Grid>
</UnstyledButton>
<Sheet title={badge.name + ' Badge Holders'} {...badgeSheet.props}>
<Box mx='xl'>
{earnedBadges?.map((earnedBadge) => (
<Text>
{earnedBadge.player.first_name +
' ' +
earnedBadge.player.last_name}
</Text>
))}
</Box>
<PlayerList players={playersForList} />
</Sheet>
{!isLastRow && <Divider />}
</Box>

View File

@@ -1,4 +1,4 @@
import { HouseIcon, RankingIcon, SealIcon, ShieldIcon, TrophyIcon, UserCircleIcon } from "@phosphor-icons/react";
import { HouseIcon, RankingIcon, ShieldIcon, TrophyIcon, UserCircleIcon } from "@phosphor-icons/react";
import { useMemo } from "react";
export const useLinks = (userId: string | undefined, roles: string[]) =>
@@ -25,11 +25,6 @@ export const useLinks = (userId: string | undefined, roles: string[]) =>
href: `/profile/${userId}`,
Icon: UserCircleIcon,
include: ['/settings']
},
{
label: 'Badges',
href: '/badges',
Icon: SealIcon
}
]

View File

@@ -1,5 +1,6 @@
import { Box, Stack, Text, Divider, Group, Button } from "@mantine/core";
import { Box, Stack, Text, Divider, Group, Button, Anchor } from "@mantine/core";
import { Suspense, useState, useDeferredValue } from "react";
import { Link } from "@tanstack/react-router";
import Header from "./header";
import SwipeableTabs from "@/components/swipeable-tabs";
import { usePlayer, usePlayerMatches, usePlayerStats } from "../../queries";
@@ -51,7 +52,12 @@ const Profile = ({ id }: ProfileProps) => {
label: "Overview",
content: <>
<Stack px="md">
<Text size="md" fw={700}>Badges</Text>
<Group justify="space-between" align="center">
<Text size="md" fw={700}>Badges</Text>
<Anchor component={Link} to="/badges" size="sm" fw={500}>
View all badges
</Anchor>
</Group>
<Suspense fallback={<BadgeShowcaseSkeleton />}>
<BadgeShowcase playerId={id} />
</Suspense>