nextjs-rewrite (#5)

Reviewed-on: #5
Co-authored-by: David Gwilliam <dhgwilliam@gmail.com>
Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
This commit was merged in pull request #5.
This commit is contained in:
2026-03-30 02:30:13 +00:00
committed by david
parent 1a9b3496e1
commit 123df671f5
160 changed files with 19293 additions and 1180 deletions
+174
View File
@@ -0,0 +1,174 @@
import { prisma } from "@/lib/prisma"
import Navigation from "@/components/Navigation"
import Link from "next/link"
import { notFound } from "next/navigation"
interface PageProps {
params: {
id: string
}
}
export default async function PlayerProfilePage({ params }: PageProps) {
const playerId = parseInt(params.id)
const player = await prisma.player.findUnique({
where: { id: playerId },
})
if (!player) {
notFound()
}
// Get partnership stats separately
const partnershipStats = await prisma.partnershipStat.findMany({
where: {
OR: [
{ player1Id: playerId },
{ player2Id: playerId },
],
},
include: {
player1: true,
player2: true,
},
orderBy: { gamesPlayed: "desc" },
})
// Use direct player stats for overall statistics (more accurate and consistent with rankings page)
const totalGames = player.gamesPlayed
const totalWins = player.wins
const winRate = totalGames > 0 ? ((totalWins / totalGames) * 100).toFixed(1) : "0.0"
// Get best partnership
const bestPartnership = partnershipStats.reduce((best, stat) => {
if (stat.gamesPlayed < 3) return best // Need at least 3 games for partnership to be meaningful
const currentRate = stat.wins / stat.gamesPlayed
const bestRate = best ? best.wins / best.gamesPlayed : 0
return currentRate > bestRate ? stat : best
}, null as (typeof partnershipStats[0] | null))
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
{/* Player Header */}
<div className="bg-white shadow rounded-lg p-6 mb-6">
<div className="flex items-center">
<div className="h-20 w-20 bg-green-100 rounded-full flex items-center justify-center text-green-800 text-2xl font-bold">
{player.name.charAt(0).toUpperCase()}
</div>
<div className="ml-6">
<h1 className="text-2xl font-bold text-gray-900">{player.name}</h1>
<p className="text-gray-500">
Member since {new Date(player.createdAt).toLocaleDateString()}
</p>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
<div className="bg-gray-50 rounded-lg p-4 text-center">
<p className="text-sm text-gray-500">Current Elo</p>
<p className="text-2xl font-bold text-gray-900">{player.currentElo}</p>
</div>
<div className="bg-gray-50 rounded-lg p-4 text-center">
<p className="text-sm text-gray-500">Total Games</p>
<p className="text-2xl font-bold text-gray-900">{totalGames}</p>
</div>
<div className="bg-gray-50 rounded-lg p-4 text-center">
<p className="text-sm text-gray-500">Win Rate</p>
<p className="text-2xl font-bold text-gray-900">{winRate}%</p>
</div>
<div className="bg-gray-50 rounded-lg p-4 text-center">
<p className="text-sm text-gray-500">Best Partner</p>
<p className="text-lg font-bold text-gray-900">
{bestPartnership
? (bestPartnership.player1Id === player.id
? bestPartnership.player2.name
: bestPartnership.player1.name)
: "N/A"}
</p>
</div>
</div>
</div>
{/* Partnership Performance */}
<div className="bg-white shadow rounded-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">
Partnership Performance
</h2>
{partnershipStats.length === 0 ? (
<p className="text-gray-500">No partnership data available yet.</p>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Partner
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Games
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Win Rate
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ELO Change
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Last Played
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{partnershipStats.map((stat) => {
const partner =
stat.player1Id === player.id ? stat.player2 : stat.player1
const winRate = stat.gamesPlayed > 0
? ((stat.wins / stat.gamesPlayed) * 100).toFixed(1)
: "0.0"
return (
<tr key={stat.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<Link
href={`/players/${partner.id}/profile`}
className="text-green-600 hover:text-green-900"
>
{partner.name}
</Link>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{stat.gamesPlayed}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{winRate}%
</td>
<td className={`px-6 py-4 whitespace-nowrap text-sm ${
stat.totalEloChange >= 0 ? 'text-green-600' : 'text-red-600'
}`}>
{stat.totalEloChange >= 0 ? '+' : ''}{stat.totalEloChange}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{stat.lastPlayed
? new Date(stat.lastPlayed).toLocaleDateString()
: "N/A"}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
)}
</div>
</div>
</main>
</div>
)
}