fix: await params in Next.js 16 routes and pages for TypeScript compatibility

This commit is contained in:
2026-03-31 00:50:56 -07:00
parent 42217e182e
commit b8d90897a4
13 changed files with 85 additions and 32 deletions
+3 -1
View File
@@ -12,7 +12,9 @@ interface PageProps {
} }
export default async function EditTournamentPage({ params }: PageProps) { export default async function EditTournamentPage({ params }: PageProps) {
const tournamentId = parseInt(params.id, 10) // Next.js 16 requires awaiting params
const { id } = await params
const tournamentId = parseInt(id, 10)
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
notFound() notFound()
+24 -6
View File
@@ -31,23 +31,41 @@ interface GameEntry {
score2: number score2: number
} }
export default function TournamentEntryPage({ params }: { params: { id: string } }) { export default function TournamentEntryPage({ params }: { params: Promise<{ id: string }> }) {
const router = useRouter() const router = useRouter()
const [tournament, setTournament] = useState<Tournament | null>(null) const [tournament, setTournament] = useState<Tournament | null>(null)
const [tournamentId, setTournamentId] = useState<number | null>(null)
const [gameText, setGameText] = useState("") const [gameText, setGameText] = useState("")
const [parsedGames, setParsedGames] = useState<GameEntry[]>([]) const [parsedGames, setParsedGames] = useState<GameEntry[]>([])
const [error, setError] = useState("") const [error, setError] = useState("")
const [success, setSuccess] = useState("") const [success, setSuccess] = useState("")
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
// Parse params and validate tournamentId
useEffect(() => { useEffect(() => {
async function parseParams() {
const { id } = await params
const parsedId = parseInt(id, 10)
if (isNaN(parsedId)) {
router.push('/admin/tournaments')
} else {
setTournamentId(parsedId)
}
}
parseParams()
}, [params, router])
// Load tournament when tournamentId is available
useEffect(() => {
if (tournamentId) {
loadTournament() loadTournament()
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [tournamentId])
const loadTournament = async () => { const loadTournament = async () => {
try { try {
const response = await fetch(`/api/tournaments/${params.id}`) const response = await fetch(`/api/tournaments/${tournamentId}`)
const data = await response.json() const data = await response.json()
if (response.ok) { if (response.ok) {
setTournament(data.tournament) setTournament(data.tournament)
@@ -103,7 +121,7 @@ export default function TournamentEntryPage({ params }: { params: { id: string }
setIsLoading(true) setIsLoading(true)
try { try {
const response = await fetch(`/api/tournaments/${params.id}/games/bulk`, { const response = await fetch(`/api/tournaments/${tournamentId}/games/bulk`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -154,7 +172,7 @@ export default function TournamentEntryPage({ params }: { params: { id: string }
<div className="px-4 py-6 sm:px-0"> <div className="px-4 py-6 sm:px-0">
<div className="mb-6"> <div className="mb-6">
<button <button
onClick={() => router.push(`/admin/tournaments/${params.id}`)} onClick={() => router.push(`/admin/tournaments/${tournamentId}`)}
className="text-green-600 hover:text-green-800 mb-4" className="text-green-600 hover:text-green-800 mb-4"
> >
Back to Tournament Back to Tournament
@@ -281,7 +299,7 @@ export default function TournamentEntryPage({ params }: { params: { id: string }
<div className="flex justify-end space-x-3"> <div className="flex justify-end space-x-3">
<button <button
onClick={() => router.push(`/admin/tournaments/${params.id}`)} onClick={() => router.push(`/admin/tournaments/${tournamentId}`)}
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500" className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
> >
Cancel Cancel
+3 -1
View File
@@ -13,7 +13,9 @@ interface PageProps {
} }
export default async function TournamentDetailPage({ params }: PageProps) { export default async function TournamentDetailPage({ params }: PageProps) {
const tournamentId = parseInt(params.id, 10) // Next.js 16 requires awaiting params
const { id } = await params
const tournamentId = parseInt(id, 10)
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
notFound() notFound()
@@ -12,7 +12,9 @@ interface PageProps {
} }
export default async function TournamentResultsPage({ params }: PageProps) { export default async function TournamentResultsPage({ params }: PageProps) {
const tournamentId = parseInt(params.id, 10) // Next.js 16 requires awaiting params
const { id } = await params
const tournamentId = parseInt(id, 10)
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
notFound() notFound()
+3 -2
View File
@@ -10,10 +10,11 @@ import { hasRole } from "@/lib/permissions";
*/ */
export async function PATCH( export async function PATCH(
request: Request, request: Request,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const playerId = parseInt(params.id); const { id } = await params
const playerId = parseInt(id);
// Validate player ID // Validate player ID
if (isNaN(playerId)) { if (isNaN(playerId)) {
@@ -8,10 +8,11 @@ const K_FACTOR = 32; // Standard K-factor for Elo calculations
export async function POST( export async function POST(
request: Request, request: Request,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const tournamentId = parseInt(params.id, 10); const { id } = await params
const tournamentId = parseInt(id, 10);
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
return NextResponse.json( return NextResponse.json(
@@ -4,10 +4,11 @@ import { canManageTournament } from "@/lib/permissions";
export async function POST( export async function POST(
request: Request, request: Request,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const tournamentId = parseInt(params.id, 10); const { id } = await params
const tournamentId = parseInt(id, 10);
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
return NextResponse.json( return NextResponse.json(
+8 -5
View File
@@ -4,9 +4,9 @@ import { canManageTournament, canDeleteTournament } from "@/lib/permissions";
import { getTournamentStatus } from "@/lib/tournamentUtils"; import { getTournamentStatus } from "@/lib/tournamentUtils";
interface RouteParams { interface RouteParams {
params: { params: Promise<{
id: string; id: string;
}; }>;
} }
/** /**
@@ -16,7 +16,8 @@ interface RouteParams {
*/ */
export async function GET(request: Request, { params }: RouteParams) { export async function GET(request: Request, { params }: RouteParams) {
try { try {
const tournamentId = parseInt(params.id); const { id } = await params
const tournamentId = parseInt(id);
// Validate tournament ID // Validate tournament ID
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
@@ -105,7 +106,8 @@ export async function GET(request: Request, { params }: RouteParams) {
*/ */
export async function PUT(request: Request, { params }: RouteParams) { export async function PUT(request: Request, { params }: RouteParams) {
try { try {
const tournamentId = parseInt(params.id); const { id } = await params
const tournamentId = parseInt(id);
// Validate tournament ID // Validate tournament ID
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
@@ -219,7 +221,8 @@ export async function PUT(request: Request, { params }: RouteParams) {
*/ */
export async function DELETE(request: Request, { params }: RouteParams) { export async function DELETE(request: Request, { params }: RouteParams) {
try { try {
const tournamentId = parseInt(params.id); const { id } = await params
const tournamentId = parseInt(id);
// Validate tournament ID // Validate tournament ID
if (isNaN(tournamentId)) { if (isNaN(tournamentId)) {
+6 -4
View File
@@ -5,9 +5,10 @@ import { canAssignTournamentAdmin } from '@/lib/permissions';
export async function GET( export async function GET(
request: NextRequest, request: NextRequest,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const { id } = await params
const session = await getSession(); const session = await getSession();
if (!session) { if (!session) {
@@ -25,12 +26,12 @@ export async function GET(
return NextResponse.json({ error: 'Requesting user not found' }, { status: 404 }); return NextResponse.json({ error: 'Requesting user not found' }, { status: 404 });
} }
if (session.user.id !== params.id && requestingUser.role !== 'club_admin') { if (session.user.id !== id && requestingUser.role !== 'club_admin') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
} }
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { id: params.id }, where: { id },
select: { role: true } select: { role: true }
}); });
@@ -46,9 +47,10 @@ export async function GET(
export async function PATCH( export async function PATCH(
request: NextRequest, request: NextRequest,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const { id } = await params
// Check if the current user has permission to assign roles // Check if the current user has permission to assign roles
const permission = await canAssignTournamentAdmin(); const permission = await canAssignTournamentAdmin();
if (!permission.allowed) { if (!permission.allowed) {
+6 -4
View File
@@ -5,9 +5,10 @@ import { canEditUserProfiles } from '@/lib/permissions';
export async function GET( export async function GET(
request: NextRequest, request: NextRequest,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const { id } = await params
const session = await getSession(); const session = await getSession();
if (!session) { if (!session) {
@@ -25,13 +26,13 @@ export async function GET(
} }
// Allow if requesting own profile or if user is club_admin // Allow if requesting own profile or if user is club_admin
const canView = session.user.id === params.id || requestingUser.role === 'club_admin'; const canView = session.user.id === id || requestingUser.role === 'club_admin';
if (!canView) { if (!canView) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
} }
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { id: params.id }, where: { id },
include: { include: {
player: true, player: true,
} }
@@ -49,9 +50,10 @@ export async function GET(
export async function PATCH( export async function PATCH(
request: NextRequest, request: NextRequest,
{ params }: { params: { id: string } } { params }: { params: Promise<{ id: string }> }
) { ) {
try { try {
const { id } = await params
const session = await getSession(); const session = await getSession();
if (!session) { if (!session) {
+3 -1
View File
@@ -10,7 +10,9 @@ interface PageProps {
} }
export default async function PlayerProfilePage({ params }: PageProps) { export default async function PlayerProfilePage({ params }: PageProps) {
const playerId = parseInt(params.id, 10) // Next.js 16 requires awaiting params
const { id } = await params
const playerId = parseInt(id, 10)
if (isNaN(playerId)) { if (isNaN(playerId)) {
notFound() notFound()
+3 -1
View File
@@ -10,7 +10,9 @@ interface PageProps {
} }
export default async function PlayerSchedulePage({ params }: PageProps) { export default async function PlayerSchedulePage({ params }: PageProps) {
const playerId = parseInt(params.id, 10) // Next.js 16 requires awaiting params
const { id } = await params
const playerId = parseInt(id, 10)
if (isNaN(playerId)) { if (isNaN(playerId)) {
notFound() notFound()
+16 -1
View File
@@ -8,6 +8,21 @@ const globalForPrisma = globalThis as unknown as {
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter }) // Create PrismaClient with adapter
const createPrismaClient = () => {
const client = new PrismaClient({ adapter })
// Add logging if in development
if (process.env.NODE_ENV !== 'production') {
client.$on('query', (e) => {
console.log('Query:', e.query)
console.log('Duration:', e.duration, 'ms')
})
}
return client
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma