diff --git a/src/app/stream-card/[id]/_components/stream-card-client.tsx b/src/app/stream-card/[id]/_components/stream-card-client.tsx new file mode 100644 index 0000000..43d0ddf --- /dev/null +++ b/src/app/stream-card/[id]/_components/stream-card-client.tsx @@ -0,0 +1,137 @@ +'use client' + +import { RANKED_CHANNEL } from '@/shared/constants' +import { api } from '@/trpc/react' +import { ArrowDown, ArrowUp } from 'lucide-react' +import { useParams } from 'next/navigation' +import { useEffect } from 'react' + +export function StreamCardClient() { + const { id } = useParams() + if (!id || typeof id !== 'string') { + return null + } + + const [gamesQueryResult, gamesQuery] = + api.history.user_games.useSuspenseQuery({ user_id: id }) + const games = gamesQueryResult || [] // Ensure games is always an array + + const [rankedUserRank, rankedUserQuery] = + api.leaderboard.get_user_rank.useSuspenseQuery({ + channel_id: RANKED_CHANNEL, + user_id: id, + }) + + useEffect(() => { + setInterval( + () => { + gamesQuery.refetch() + rankedUserQuery.refetch() + }, + // 3 minutes + 1000 * 60 * 3 + ) + }, []) + + if (!rankedUserRank || !games?.length) { + return null + } + const filteredGamesByLeaderboard = games.filter( + (game) => game.gameType === 'ranked' + ) + + const games_played = filteredGamesByLeaderboard.length + let wins = 0 + let losses = 0 + let ties = 0 + for (const game of filteredGamesByLeaderboard) { + if (game.result === 'win') { + wins++ + } else if (game.result === 'loss') { + losses++ + } else if (game.result === 'tie') { + ties++ + } else { + ties++ + } + } + + const lastGame = filteredGamesByLeaderboard.at(0) + + const currentName = lastGame?.playerName + const meaningful_games = games_played - ties + const playerData = { + username: currentName, + games: games_played, + meaningful_games, + wins, + losses, + ties, + winRate: + meaningful_games > 0 ? Math.ceil((wins / meaningful_games) * 100) : 0, + lossRate: + meaningful_games > 0 ? Math.floor((losses / meaningful_games) * 100) : 0, + rank: rankedUserRank.rank, + mmr: Math.round(rankedUserRank.mmr), + mmrChange: `${(lastGame?.mmrChange ?? 0) >= 0 ? '+' : '-'}${Math.round(lastGame?.mmrChange ?? 0)}`, + streak: rankedUserRank?.streak, + } + return ( +
+
+ {playerData.rank} +
+ + {/* Player Name */} +
+
{playerData.username}
+
+ + {/* MMR */} +
+
MMR:
+
{playerData.mmr}
+
{playerData.mmrChange}
+
+ + {/* Win Rate */} +
+
Win Rate:
+
+ {playerData.winRate}% +
+
+ + {/* Win/Loss */} +
+
+ +
+ {playerData.wins} +
+
+
+ +
+ {playerData.losses} +
+
+
+ + {/* Streak */} +
+
Streak:
+
{playerData.streak}
+
+ + {/* Live Indicator */} +
+
+ Live +
+
+ ) +} diff --git a/src/app/stream-card/[id]/page.tsx b/src/app/stream-card/[id]/page.tsx index 5571747..876ea75 100644 --- a/src/app/stream-card/[id]/page.tsx +++ b/src/app/stream-card/[id]/page.tsx @@ -1,123 +1,32 @@ -'use client' - +import { StreamCardClient } from '@/app/stream-card/[id]/_components/stream-card-client' import { RANKED_CHANNEL } from '@/shared/constants' -import { api } from '@/trpc/react' -import { ArrowDown, ArrowUp } from 'lucide-react' -import { useParams } from 'next/navigation' +import { HydrateClient, api } from '@/trpc/server' +import { Suspense } from 'react' -export default function LeaderboardCard() { - const { id } = useParams() - if (!id || typeof id !== 'string') { - return null +export default async function StreamCardPage({ + params, +}: { + params: Promise<{ id: string }> +}) { + const { id } = await params + if (id) { + await Promise.all([ + api.history.user_games.prefetch({ + user_id: id, + }), + + api.leaderboard.get_user_rank.prefetch({ + channel_id: RANKED_CHANNEL, + user_id: id, + }), + ]) } - const gamesQuery = api.history.user_games.useQuery({ user_id: id }) - const games = gamesQuery?.data || [] // Ensure games is always an array - - const { data: rankedUserRank } = api.leaderboard.get_user_rank.useQuery({ - channel_id: RANKED_CHANNEL, - user_id: id, - }) - - if (!rankedUserRank || !games?.length) { - return null - } - const filteredGamesByLeaderboard = games.filter( - (game) => game.gameType === 'ranked' - ) - - const games_played = filteredGamesByLeaderboard.length - let wins = 0 - let losses = 0 - let ties = 0 - for (const game of filteredGamesByLeaderboard) { - if (game.result === 'win') { - wins++ - } else if (game.result === 'loss') { - losses++ - } else if (game.result === 'tie') { - ties++ - } else { - ties++ - } - } - - const lastGame = filteredGamesByLeaderboard.at(0) - - const currentName = lastGame?.playerName - const meaningful_games = games_played - ties - const playerData = { - username: currentName, - games: games_played, - meaningful_games, - wins, - losses, - ties, - winRate: - meaningful_games > 0 ? Math.ceil((wins / meaningful_games) * 100) : 0, - lossRate: - meaningful_games > 0 ? Math.floor((losses / meaningful_games) * 100) : 0, - rank: rankedUserRank.rank, - mmr: Math.round(rankedUserRank.mmr), - mmrChange: `${(lastGame?.mmrChange ?? 0) >= 0 ? '+' : '-'}${Math.round(lastGame?.mmrChange ?? 0)}`, - streak: rankedUserRank?.streak, - } return ( -
-
- {playerData.rank} -
- - {/* Player Name */} -
-
{playerData.username}
-
- - {/* MMR */} -
-
MMR:
-
{playerData.mmr}
-
{playerData.mmrChange}
-
- - {/* Win Rate */} -
-
Win Rate:
-
- {playerData.winRate}% -
-
- - {/* Win/Loss */} -
-
- -
- {playerData.wins} -
-
-
- -
- {playerData.losses} -
-
-
- - {/* Streak */} -
-
Streak:
-
{playerData.streak}
-
- - {/* Live Indicator */} -
-
- Live -
-
+ + + + + ) }