This commit is contained in:
Linrador 2025-10-15 20:27:54 +02:00
parent 86e9b53b78
commit fb200876d0
2 changed files with 50 additions and 51 deletions

View File

@ -0,0 +1,47 @@
// /src/app/[locale]/match-details/[matchId]/ClientMatchLoader.tsx
'use client'
import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { MatchProvider } from './MatchContext'
import type { Match } from '../../../../types/match'
export default function ClientMatchLoader({ children }: { children: React.ReactNode }) {
const router = useRouter()
const params = useParams() as { matchId?: string | string[] }
const matchId = Array.isArray(params.matchId) ? params.matchId?.[0] : params.matchId
const [match, setMatch] = useState<Match | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let alive = true
if (!matchId) { router.replace('/404'); return }
;(async () => {
try {
const res = await fetch(`/api/matches/${encodeURIComponent(matchId)}`, { cache: 'no-store' })
if (!res.ok) { router.replace('/404'); return }
const data = (await res.json()) as Match
if (alive) setMatch(data)
} catch {
router.replace('/404')
} finally {
if (alive) setLoading(false)
}
})()
return () => { alive = false }
}, [matchId, router])
if (loading) {
return (
<div className="p-6 text-sm text-gray-500 dark:text-neutral-400">
Lädt Match
</div>
)
}
if (!match) return null
return <MatchProvider match={match}>{children}</MatchProvider>
}

View File

@ -1,57 +1,9 @@
// /src/app/[locale]/match-details/[matchId]/layout.tsx
import { headers } from 'next/headers'
import { notFound } from 'next/navigation'
import { MatchProvider } from './MatchContext'
import type { Match } from '../../../../types/match'
import ClientMatchLoader from './ClientMatchLoader'
export const dynamic = 'force-dynamic'
export const revalidate = 0
async function buildOrigin(): Promise<string> {
const h = await headers()
const proto = (h.get('x-forwarded-proto') ?? 'http').split(',')[0].trim()
const host = (h.get('x-forwarded-host') ?? h.get('host') ?? '').split(',')[0].trim()
if (host) return `${proto}://${host}`
// Fallbacks (lokale Entwicklung / SSR-Tools)
return (
process.env.NEXT_PUBLIC_SITE_URL ||
process.env.NEXTAUTH_URL ||
'http://localhost:3000'
)
}
async function loadMatch(matchId: string): Promise<Match | null> {
const origin = await buildOrigin()
// ⚠️ Dev-Only: Selbstsignierte Zertifikate erlauben
const allowInsecure =
process.env.NODE_ENV !== 'production' ||
process.env.ALLOW_INSECURE_FETCH === '1'
if (origin.startsWith('https://') && allowInsecure) {
// global für den Node-Prozess bitte NICHT in Production setzen
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
}
const res = await fetch(`${origin}/api/matches/${encodeURIComponent(matchId)}`, {
cache: 'no-store',
})
if (!res.ok) return null
return res.json()
}
type Params = { matchId: string }
type Props = {
children: React.ReactNode
params: Promise<Params>
}
export default async function MatchLayout({ children, params }: Props) {
const { matchId } = await params
const match = await loadMatch(matchId)
if (!match) notFound()
return <MatchProvider match={match}>{children}</MatchProvider>
export default function MatchLayout({ children }: { children: React.ReactNode }) {
return <ClientMatchLoader>{children}</ClientMatchLoader>
}