fix table styles

This commit is contained in:
2025-04-06 19:35:12 +02:00
parent 515fc0780d
commit d2f4f74a13
5 changed files with 353 additions and 292 deletions

View File

@@ -9,7 +9,6 @@ export default async function PlayerPage({
}: { }: {
params: Promise<{ id: string }> params: Promise<{ id: string }>
}) { }) {
const session = await auth()
const { id } = await params const { id } = await params
if (id) { if (id) {
await Promise.all([ await Promise.all([

View File

@@ -5,14 +5,13 @@ import {
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from '@/components/ui/tooltip' } from '@/components/ui/mobile-tooltip'
import type React from 'react' import type React from 'react'
import { useState } from 'react' import { useState } from 'react'
import { GamesTable } from '@/app/players/[id]/_components/games-table' import { GamesTable } from '@/app/players/[id]/_components/games-table'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -78,7 +77,6 @@ export function UserInfo() {
channel_id: RANKED_CHANNEL, channel_id: RANKED_CHANNEL,
user_id: id, user_id: id,
}) })
console.log({ vanillaUserRank, rankedUserRank })
// Filter games by leaderboard if needed // Filter games by leaderboard if needed
const filteredGamesByLeaderboard = const filteredGamesByLeaderboard =
@@ -142,304 +140,298 @@ export function UserInfo() {
.at(0) .at(0)
return ( return (
<div className='min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 dark:from-zinc-900 dark:to-zinc-950'> <div className='flex flex-1 flex-col overflow-hidden'>
<div className='container mx-auto'> <div className='mx-auto flex w-[calc(100%-1rem)] max-w-fd-container flex-1 flex-col'>
<Card className='overflow-hidden border-none bg-white py-0 shadow-lg dark:bg-zinc-900'> <div className='py-8'>
<CardHeader className='border-gray-200 border-b bg-white p-6 dark:border-zinc-800 dark:bg-zinc-900'> <div className='flex flex-col items-center gap-6 md:flex-row'>
<div className='flex flex-col items-center gap-6 md:flex-row'> <div className='relative'>
<div className='relative'> <Avatar className='size-24'>
<Avatar className='h-24 w-24 border-4 border-gray-100 shadow-md dark:border-zinc-800'> <AvatarImage
<AvatarImage src={profileData.avatar}
src={profileData.avatar} alt={profileData.username}
alt={profileData.username} />
/> <AvatarFallback className='bg-violet-50 font-bold text-2xl text-violet-600 dark:bg-violet-900/30 dark:text-violet-300'>
<AvatarFallback className='bg-violet-50 font-bold text-2xl text-violet-600 dark:bg-violet-900/30 dark:text-violet-300'> {profileData.username.slice(0, 2).toUpperCase()}
{profileData.username.slice(0, 2).toUpperCase()} </AvatarFallback>
</AvatarFallback> </Avatar>
</Avatar> </div>
<div className='text-center md:text-left'>
<div className={'flex items-start gap-2'}>
<h1 className='font-bold text-3xl text-gray-900 dark:text-white'>
{profileData.username}
</h1>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className={'size-4'} />
</TooltipTrigger>
<TooltipContent align={'center'} sideOffset={5}>
<div>
<p>Also known as:</p>
<ul className={'list-disc pl-4'}>
{aliases.map((alias) => (
<li key={alias}>{alias}</li>
))}
</ul>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
<div className='text-center md:text-left'> <p className='text-gray-500 text-sm dark:text-zinc-400'>
<div className={'flex items-start gap-2'}> {firstGame ? (
<h1 className='font-bold text-3xl text-gray-900 dark:text-white'> <>First game: {dateFormatter.format(firstGame.gameTime)}</>
{profileData.username} ) : (
</h1> <>No games played yet</>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className={'size-4'} />
</TooltipTrigger>
<TooltipContent align={'center'} sideOffset={5}>
<div>
<p>Also known as:</p>
<ul className={'list-disc pl-4'}>
{aliases.map((alias) => (
<li key={alias}>{alias}</li>
))}
</ul>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<p className='text-gray-500 text-sm dark:text-zinc-400'>
{firstGame ? (
<>First game: {dateFormatter.format(firstGame.gameTime)}</>
) : (
<>No games played yet</>
)}
</p>
<div className='mt-2 flex flex-wrap items-center justify-center gap-2 md:justify-start'>
{!!rankedLeaderboard && (
<Badge
variant='outline'
className='border-gray-200 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-800'
>
<Trophy className='mr-1 h-3 w-3 text-violet-500' />
<span className='text-gray-700 dark:text-zinc-300'>
Ranked Queue:{' '}
{isNonNullish(rankedUserRank?.rank)
? `#${rankedUserRank.rank}`
: 'N/A'}
</span>
</Badge>
)}
{!!vanillaLeaderboard && (
<Badge
variant='outline'
className='border-gray-200 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-800'
>
<Trophy className='mr-1 h-3 w-3 text-violet-500' />
<span className='text-gray-700 dark:text-zinc-300'>
Vanilla Queue:{' '}
{isNonNullish(vanillaUserRank?.rank)
? `#${vanillaUserRank.rank}`
: 'N/A'}
</span>
</Badge>
)}
</div>
</div>
<div
className={cn(
'grid w-full flex-grow grid-cols-2 divide-gray-100 md:w-auto md:grid-cols-3 md:divide-y-0 dark:divide-zinc-800',
isNonNullish(rankedUserRank?.mmr) && 'lg:grid-cols-4',
isNonNullish(vanillaUserRank?.mmr) && 'lg:grid-cols-4',
isNonNullish(rankedUserRank?.mmr) &&
isNonNullish(vanillaUserRank?.mmr) &&
'lg:grid-cols-5'
)} )}
> </p>
<StatsCard <div className='mt-2 flex flex-wrap items-center justify-center gap-2 md:justify-start'>
title='Games' {!!rankedLeaderboard && (
value={profileData.games} <Badge
icon={<BarChart3 className='h-5 w-5 text-violet-500' />} variant='outline'
description='Total matches' className='border-gray-200 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-800'
/> >
<StatsCard <Trophy className='mr-1 h-3 w-3 text-violet-500' />
title='Wins' <span className='text-gray-700 dark:text-zinc-300'>
value={profileData.wins} Ranked Queue:{' '}
icon={<ArrowUpCircle className='h-5 w-5 text-emerald-500' />} {isNonNullish(rankedUserRank?.rank)
description={`${profileData.winRate}% win rate`} ? `#${rankedUserRank.rank}`
accentColor='text-emerald-500' : 'N/A'}
/> </span>
<StatsCard </Badge>
title='Losses'
value={profileData.losses}
icon={<ArrowDownCircle className='h-5 w-5 text-rose-500' />}
description={`${profileData.lossRate}% loss rate`}
accentColor='text-rose-500'
/>
{isNonNullish(rankedUserRank?.mmr) && (
<StatsCard
title='Ranked MMR'
value={Math.round(rankedUserRank.mmr)}
description={
lastRankedGame ? (
<span
className={cn(
'flex items-center',
lastRankedGame.mmrChange === 0
? 'text-zink-800 dark:text-zink-200'
: lastRankedGame.mmrChange > 0
? 'text-emerald-500'
: 'text-rose-500'
)}
>
{lastRankedGame.mmrChange === 0 ? (
'Tied'
) : lastRankedGame.mmrChange > 0 ? (
<ChevronUp className='h-3 w-3' />
) : (
<ChevronDown className='h-3 w-3' />
)}
{lastRankedGame.mmrChange !== 0
? numberFormatter.format(
Math.trunc(lastRankedGame.mmrChange)
)
: null}{' '}
last match
</span>
) : null
}
icon={
<ShieldHalf className='h-5 w-5 text-zink-800 dark:text-zink-200' />
}
accentColor='text-zink-800 dark:text-zink-200'
/>
)} )}
{isNonNullish(vanillaUserRank?.mmr) && ( {!!vanillaLeaderboard && (
<StatsCard <Badge
title='Vanilla MMR' variant='outline'
value={Math.round(vanillaUserRank.mmr)} className='border-gray-200 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-800'
icon={ >
<IceCreamCone className='h-5 w-5 text-zink-800 dark:text-zink-200' /> <Trophy className='mr-1 h-3 w-3 text-violet-500' />
} <span className='text-gray-700 dark:text-zinc-300'>
accentColor='text-zink-800 dark:text-zink-200' Vanilla Queue:{' '}
description={ {isNonNullish(vanillaUserRank?.rank)
lastVanillaGame ? ( ? `#${vanillaUserRank.rank}`
<span : 'N/A'}
className={cn( </span>
'flex items-center', </Badge>
lastVanillaGame.mmrChange === 0
? 'text-zink-800 dark:text-zink-200'
: lastVanillaGame.mmrChange > 0
? 'text-emerald-500'
: 'text-rose-500'
)}
>
{lastVanillaGame.mmrChange === 0 ? (
'Tied'
) : lastVanillaGame.mmrChange > 0 ? (
<ChevronUp className='h-3 w-3' />
) : (
<ChevronDown className='h-3 w-3' />
)}
{lastVanillaGame.mmrChange !== 0
? numberFormatter.format(
Math.trunc(lastVanillaGame.mmrChange)
)
: null}{' '}
last match
</span>
) : null
}
/>
)} )}
</div> </div>
</div> </div>
</CardHeader> <div
className={cn(
'grid w-full flex-grow grid-cols-2 divide-gray-100 md:w-auto md:grid-cols-3 md:divide-y-0 dark:divide-zinc-800',
isNonNullish(rankedUserRank?.mmr) && 'lg:grid-cols-4',
isNonNullish(vanillaUserRank?.mmr) && 'lg:grid-cols-4',
isNonNullish(rankedUserRank?.mmr) &&
isNonNullish(vanillaUserRank?.mmr) &&
'lg:grid-cols-5'
)}
>
<StatsCard
title='Games'
value={profileData.games}
icon={<BarChart3 className='h-5 w-5 text-violet-500' />}
description='Total matches'
/>
<StatsCard
title='Wins'
value={profileData.wins}
icon={<ArrowUpCircle className='h-5 w-5 text-emerald-500' />}
description={`${profileData.winRate}% win rate`}
accentColor='text-emerald-500'
/>
<StatsCard
title='Losses'
value={profileData.losses}
icon={<ArrowDownCircle className='h-5 w-5 text-rose-500' />}
description={`${profileData.lossRate}% loss rate`}
accentColor='text-rose-500'
/>
{isNonNullish(rankedUserRank?.mmr) && (
<StatsCard
title='Ranked MMR'
value={Math.round(rankedUserRank.mmr)}
description={
lastRankedGame ? (
<span
className={cn(
'flex items-center',
lastRankedGame.mmrChange === 0
? 'text-zink-800 dark:text-zink-200'
: lastRankedGame.mmrChange > 0
? 'text-emerald-500'
: 'text-rose-500'
)}
>
{lastRankedGame.mmrChange === 0 ? (
'Tied'
) : lastRankedGame.mmrChange > 0 ? (
<ChevronUp className='h-3 w-3' />
) : (
<ChevronDown className='h-3 w-3' />
)}
{lastRankedGame.mmrChange !== 0
? numberFormatter.format(
Math.trunc(lastRankedGame.mmrChange)
)
: null}{' '}
last match
</span>
) : null
}
icon={
<ShieldHalf className='h-5 w-5 text-zink-800 dark:text-zink-200' />
}
accentColor='text-zink-800 dark:text-zink-200'
/>
)}
{isNonNullish(vanillaUserRank?.mmr) && (
<StatsCard
title='Vanilla MMR'
value={Math.round(vanillaUserRank.mmr)}
icon={
<IceCreamCone className='h-5 w-5 text-zink-800 dark:text-zink-200' />
}
accentColor='text-zink-800 dark:text-zink-200'
description={
lastVanillaGame ? (
<span
className={cn(
'flex items-center',
lastVanillaGame.mmrChange === 0
? 'text-zink-800 dark:text-zink-200'
: lastVanillaGame.mmrChange > 0
? 'text-emerald-500'
: 'text-rose-500'
)}
>
{lastVanillaGame.mmrChange === 0 ? (
'Tied'
) : lastVanillaGame.mmrChange > 0 ? (
<ChevronUp className='h-3 w-3' />
) : (
<ChevronDown className='h-3 w-3' />
)}
{lastVanillaGame.mmrChange !== 0
? numberFormatter.format(
Math.trunc(lastVanillaGame.mmrChange)
)
: null}{' '}
last match
</span>
) : null
}
/>
)}
</div>
</div>
</div>
<CardContent className='p-0'> <Tabs defaultValue='matches' className='p-6'>
<Tabs defaultValue='matches' className='p-6'> <div className='mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center'>
<div className='mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center'> <TabsList className='bg-gray-100 dark:bg-zinc-800'>
<TabsList className='bg-gray-100 dark:bg-zinc-800'> <TabsTrigger value='matches'>Match History</TabsTrigger>
<TabsTrigger value='matches'>Match History</TabsTrigger> <TabsTrigger value='stats'>Statistics</TabsTrigger>
<TabsTrigger value='stats'>Statistics</TabsTrigger> <TabsTrigger value='achievements'>Achievements</TabsTrigger>
<TabsTrigger value='achievements'>Achievements</TabsTrigger> </TabsList>
</TabsList>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<div className='mr-2 flex items-center gap-2'> <div className='mr-2 flex items-center gap-2'>
<Trophy className='h-4 w-4 text-gray-400 dark:text-zinc-400' /> <Trophy className='h-4 w-4 text-gray-400 dark:text-zinc-400' />
<Select <Select
value={leaderboardFilter} value={leaderboardFilter}
onValueChange={setLeaderboardFilter} onValueChange={setLeaderboardFilter}
> >
<SelectTrigger className='h-9 w-[150px]'> <SelectTrigger className='h-9 w-[150px]'>
<SelectValue placeholder='Leaderboard' /> <SelectValue placeholder='Leaderboard' />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value='all'>All Leaderboards</SelectItem> <SelectItem value='all'>All Leaderboards</SelectItem>
<SelectItem value='ranked'>Ranked</SelectItem> <SelectItem value='ranked'>Ranked</SelectItem>
<SelectItem value='vanilla'>Vanilla</SelectItem> <SelectItem value='vanilla'>Vanilla</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div>
<Filter className='h-4 w-4 text-gray-400 dark:text-zinc-400' />
<Select value={filter} onValueChange={setFilter}>
<SelectTrigger className='h-9 w-[120px]'>
<SelectValue placeholder='Filter' />
</SelectTrigger>
<SelectContent>
<SelectItem value='all'>All Games</SelectItem>
<SelectItem value='wins'>Wins</SelectItem>
<SelectItem value='losses'>Losses</SelectItem>
<SelectItem value='ties'>Ties</SelectItem>
</SelectContent>
</Select>
</div>
</div> </div>
<TabsContent value='matches' className='m-0'> <Filter className='h-4 w-4 text-gray-400 dark:text-zinc-400' />
<div className='overflow-hidden rounded-lg border'> <Select value={filter} onValueChange={setFilter}>
<div className='overflow-x-auto'> <SelectTrigger className='h-9 w-[120px]'>
<GamesTable games={filteredGames} /> <SelectValue placeholder='Filter' />
</SelectTrigger>
<SelectContent>
<SelectItem value='all'>All Games</SelectItem>
<SelectItem value='wins'>Wins</SelectItem>
<SelectItem value='losses'>Losses</SelectItem>
<SelectItem value='ties'>Ties</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<TabsContent value='matches' className='m-0'>
<div className='overflow-hidden rounded-lg border'>
<div className='overflow-x-auto'>
<GamesTable games={filteredGames} />
</div>
</div>
</TabsContent>
<TabsContent value='stats' className='m-0'>
<div className='grid grid-cols-1 gap-6 md:grid-cols-2'>
{(rankedLeaderboard || lastRankedGame) && (
<LeaderboardStatsCard
title='Ranked Queue Stats'
rank={rankedUserRank?.rank}
mmr={
lastRankedGame
? Math.trunc(
lastRankedGame.playerMmr + lastRankedGame.mmrChange
)
: undefined
}
icon={<Trophy className='h-5 w-5 text-violet-500' />}
accentColor='text-violet-500'
/>
)}
{(vanillaLeaderboard || lastVanillaGame) && (
<LeaderboardStatsCard
title='Vanilla Queue Stats'
rank={vanillaUserRank?.rank}
mmr={
lastVanillaGame
? Math.trunc(
lastVanillaGame.playerMmr + lastVanillaGame.mmrChange
)
: undefined
}
icon={<Star className='h-5 w-5 text-amber-500' />}
accentColor='text-amber-500'
/>
)}
{!rankedLeaderboard &&
!vanillaLeaderboard &&
!lastRankedGame &&
!lastVanillaGame && (
<div className='col-span-2 flex h-40 items-center justify-center rounded-lg border bg-gray-50 dark:bg-zinc-800/50'>
<p className='text-gray-500 dark:text-zinc-400'>
No leaderboard data available
</p>
</div> </div>
</div> )}
</TabsContent> </div>
</TabsContent>
<TabsContent value='stats' className='m-0'> <TabsContent value='achievements' className='m-0'>
<div className='grid grid-cols-1 gap-6 md:grid-cols-2'> <div className='flex h-40 items-center justify-center rounded-lg border bg-gray-50 dark:bg-zinc-800/50'>
{(rankedLeaderboard || lastRankedGame) && ( <p className='text-gray-500 dark:text-zinc-400'>
<LeaderboardStatsCard Achievements coming soon
title='Ranked Queue Stats' </p>
rank={rankedUserRank?.rank} </div>
mmr={ </TabsContent>
lastRankedGame </Tabs>
? Math.trunc(
lastRankedGame.playerMmr +
lastRankedGame.mmrChange
)
: undefined
}
icon={<Trophy className='h-5 w-5 text-violet-500' />}
accentColor='text-violet-500'
/>
)}
{(vanillaLeaderboard || lastVanillaGame) && (
<LeaderboardStatsCard
title='Vanilla Queue Stats'
rank={vanillaUserRank?.rank}
mmr={
lastVanillaGame
? Math.trunc(
lastVanillaGame.playerMmr +
lastVanillaGame.mmrChange
)
: undefined
}
icon={<Star className='h-5 w-5 text-amber-500' />}
accentColor='text-amber-500'
/>
)}
{!rankedLeaderboard &&
!vanillaLeaderboard &&
!lastRankedGame &&
!lastVanillaGame && (
<div className='col-span-2 flex h-40 items-center justify-center rounded-lg border bg-gray-50 dark:bg-zinc-800/50'>
<p className='text-gray-500 dark:text-zinc-400'>
No leaderboard data available
</p>
</div>
)}
</div>
</TabsContent>
<TabsContent value='achievements' className='m-0'>
<div className='flex h-40 items-center justify-center rounded-lg border bg-gray-50 dark:bg-zinc-800/50'>
<p className='text-gray-500 dark:text-zinc-400'>
Achievements coming soon
</p>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div> </div>
</div> </div>
) )

View File

@@ -169,7 +169,7 @@ export function LeaderboardPage() {
return ( return (
<div className='flex flex-1 flex-col overflow-hidden'> <div className='flex flex-1 flex-col overflow-hidden'>
<div className=' mx-auto flex w-[calc(100%-1rem)] max-w-fd-container flex-1 flex-col'> <div className='mx-auto flex w-[calc(100%-1rem)] max-w-fd-container flex-1 flex-col'>
<div className='flex flex-1 flex-col overflow-hidden border-none'> <div className='flex flex-1 flex-col overflow-hidden border-none'>
<Tabs <Tabs
defaultValue={leaderboardType} defaultValue={leaderboardType}
@@ -273,7 +273,7 @@ function RawLeaderboardTable({
<div <div
ref={tableContainerRef} ref={tableContainerRef}
className='flex-1 overflow-auto overflow-x-auto' className='flex-1 overflow-auto overflow-x-auto'
style={{ maxHeight: 'calc(100vh - 300px)' }} style={{ maxHeight: 'calc(100vh - 200px)' }}
> >
<Table> <Table>
<TableHeader className='sticky top-0 z-10 bg-white dark:bg-zinc-900'> <TableHeader className='sticky top-0 z-10 bg-white dark:bg-zinc-900'>

View File

@@ -4,7 +4,6 @@ import type { ReactNode } from 'react'
import { source } from '../../../lib/source' import { source } from '../../../lib/source'
export default function Layout({ children }: { children: ReactNode }) { export default function Layout({ children }: { children: ReactNode }) {
console.log(baseOptions)
return ( return (
<DocsLayout {...baseOptions} tree={source.pageTree}> <DocsLayout {...baseOptions} tree={source.pageTree}>
{children} {children}

View File

@@ -0,0 +1,71 @@
'use client'
import type {
PopoverContentProps,
PopoverProps,
PopoverTriggerProps,
} from '@radix-ui/react-popover'
import type {
TooltipContentProps,
TooltipProps,
TooltipProviderProps,
TooltipTriggerProps,
} from '@radix-ui/react-tooltip'
import { createContext, useContext, useEffect, useState } from 'react'
import { Popover, PopoverContent, PopoverTrigger } from './popover'
import {
Tooltip as OriginalTooltip,
TooltipContent as OriginalTooltipContent,
TooltipProvider as OriginalTooltipProvider,
TooltipTrigger as OriginalTooltipTrigger,
} from './tooltip'
const TouchContext = createContext<boolean | undefined>(undefined)
const useTouch = () => useContext(TouchContext)
export const TooltipProvider = ({
children,
...props
}: TooltipProviderProps) => {
const [isTouch, setTouch] = useState<boolean | undefined>(undefined)
useEffect(() => {
setTouch(window.matchMedia('(pointer: coarse)').matches)
}, [])
return (
<TouchContext.Provider value={isTouch}>
<OriginalTooltipProvider {...props}>{children}</OriginalTooltipProvider>
</TouchContext.Provider>
)
}
export const Tooltip = (props: TooltipProps & PopoverProps) => {
const isTouch = useTouch()
return isTouch ? <Popover {...props} /> : <OriginalTooltip {...props} />
}
export const TooltipTrigger = (
props: TooltipTriggerProps & PopoverTriggerProps
) => {
const isTouch = useTouch()
return isTouch ? (
<PopoverTrigger {...props} />
) : (
<OriginalTooltipTrigger {...props} />
)
}
export const TooltipContent = (
props: TooltipContentProps & PopoverContentProps
) => {
const isTouch = useTouch()
return isTouch ? (
<PopoverContent {...props} />
) : (
<OriginalTooltipContent {...props} />
)
}