name blurbs, skeleton and link
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import BadgeStatsTable from '@/features/badges/components/badge-stats-table';
|
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 { badgeQueries, useAllBadges } from '@/features/badges/queries';
|
||||||
import PlayerStatsTableSkeleton from '@/features/players/components/player-stats-table-skeleton';
|
import PlayerStatsTableSkeleton from '@/features/players/components/player-stats-table-skeleton';
|
||||||
import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch';
|
import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch';
|
||||||
@@ -16,15 +17,15 @@ export const Route = createFileRoute('/_authed/badges')({
|
|||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
header: {
|
header: {
|
||||||
title: 'All Badges',
|
title: 'All Badges',
|
||||||
|
withBackButton: true,
|
||||||
},
|
},
|
||||||
refresh: [badgeQueries.allBadges().queryKey],
|
refresh: [badgeQueries.allBadges().queryKey],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
function Badges() {
|
function Badges() {
|
||||||
//TODO: CHANGE FALLBACK
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<PlayerStatsTableSkeleton />}>
|
<Suspense fallback={<BadgeStatsTableSkeleton />}>
|
||||||
<div>
|
<div>
|
||||||
<BadgeStatsTable />
|
<BadgeStatsTable />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -15,6 +15,9 @@ import { Badge, EarnedBadge } from '../types';
|
|||||||
import { useAllPlayers } from '@/features/teams/hooks/use-available-players';
|
import { useAllPlayers } from '@/features/teams/hooks/use-available-players';
|
||||||
import { useSheet } from '@/hooks/use-sheet';
|
import { useSheet } from '@/hooks/use-sheet';
|
||||||
import Sheet from '@/components/sheet/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 BadgeStatsTable = () => {
|
||||||
const { data: allBadges } = useAllBadges();
|
const { data: allBadges } = useAllBadges();
|
||||||
@@ -79,16 +82,69 @@ const BadgeStatRow: React.FC<BadgeStatRowProps> = ({
|
|||||||
isLastRow,
|
isLastRow,
|
||||||
}) => {
|
}) => {
|
||||||
const badgeSheet = useSheet();
|
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 (
|
return (
|
||||||
<Box key={badge.id}>
|
<Box key={badge.id}>
|
||||||
<UnstyledButton onClick={badgeSheet.open} w='100%'>
|
<UnstyledButton onClick={badgeSheet.open} w='100%'>
|
||||||
<Grid p={'xs'}>
|
<Grid p={'xs'} align='center'>
|
||||||
<Grid.Col span={2}>
|
<Grid.Col span={2} style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<BadgeIcon badge={badge} filled={true} />
|
<BadgeIcon badge={badge} filled={true} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={'auto'}>
|
<Grid.Col span={'auto'}>
|
||||||
<Text fw={700}>{badge.name}</Text>
|
<Stack gap={2}>
|
||||||
<Text fw={500}>{badge.description}</Text>
|
<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>
|
||||||
<Grid.Col
|
<Grid.Col
|
||||||
style={{
|
style={{
|
||||||
@@ -100,28 +156,20 @@ const BadgeStatRow: React.FC<BadgeStatRowProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack gap={0} align='center'>
|
<Stack gap={0} align='center'>
|
||||||
<Text size='lg' fw={700}>
|
<Text c="dimmed" size='lg' fw={700}>
|
||||||
{(
|
{(
|
||||||
((earnedBadges?.length ?? 0) / totalNumPlayers) *
|
((earnedBadges?.length ?? 0) / totalNumPlayers) *
|
||||||
100
|
100
|
||||||
).toFixed(0)}
|
).toFixed(0)}
|
||||||
%
|
%
|
||||||
</Text>
|
</Text>
|
||||||
<Text size='xs'>of players</Text>
|
<Text c="dimmed" size='xs'>of players</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
<Sheet title={badge.name + ' Badge Holders'} {...badgeSheet.props}>
|
<Sheet title={badge.name + ' Badge Holders'} {...badgeSheet.props}>
|
||||||
<Box mx='xl'>
|
<PlayerList players={playersForList} />
|
||||||
{earnedBadges?.map((earnedBadge) => (
|
|
||||||
<Text>
|
|
||||||
{earnedBadge.player.first_name +
|
|
||||||
' ' +
|
|
||||||
earnedBadge.player.last_name}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Sheet>
|
</Sheet>
|
||||||
{!isLastRow && <Divider />}
|
{!isLastRow && <Divider />}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -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";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export const useLinks = (userId: string | undefined, roles: string[]) =>
|
export const useLinks = (userId: string | undefined, roles: string[]) =>
|
||||||
@@ -25,11 +25,6 @@ export const useLinks = (userId: string | undefined, roles: string[]) =>
|
|||||||
href: `/profile/${userId}`,
|
href: `/profile/${userId}`,
|
||||||
Icon: UserCircleIcon,
|
Icon: UserCircleIcon,
|
||||||
include: ['/settings']
|
include: ['/settings']
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Badges',
|
|
||||||
href: '/badges',
|
|
||||||
Icon: SealIcon
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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 { Suspense, useState, useDeferredValue } from "react";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
import Header from "./header";
|
import Header from "./header";
|
||||||
import SwipeableTabs from "@/components/swipeable-tabs";
|
import SwipeableTabs from "@/components/swipeable-tabs";
|
||||||
import { usePlayer, usePlayerMatches, usePlayerStats } from "../../queries";
|
import { usePlayer, usePlayerMatches, usePlayerStats } from "../../queries";
|
||||||
@@ -51,7 +52,12 @@ const Profile = ({ id }: ProfileProps) => {
|
|||||||
label: "Overview",
|
label: "Overview",
|
||||||
content: <>
|
content: <>
|
||||||
<Stack px="md">
|
<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 />}>
|
<Suspense fallback={<BadgeShowcaseSkeleton />}>
|
||||||
<BadgeShowcase playerId={id} />
|
<BadgeShowcase playerId={id} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
Reference in New Issue
Block a user