Initial commit v1

This commit is contained in:
Rolf
2026-03-18 12:37:17 +01:00
commit 4897d3003f
55 changed files with 14241 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import { createClient } from '@/lib/supabase/server'
import { redirect, notFound } from 'next/navigation'
import { JoinRound } from '@/components/round/join-round'
export default async function JoinRoundPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const supabase = await createClient()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const db = supabase as any
const {
data: { user },
} = await supabase.auth.getUser()
if (!user) redirect(`/login`)
const { data: round } = await db
.from('rounds')
.select(
`
id, name, date, holes_count, status, created_by,
course:courses (name),
tee:tees (name, color),
round_players (user_id)
`,
)
.eq('id', id)
.single()
if (!round) notFound()
// Already a member — go straight to the round
const alreadyJoined = round.round_players?.some(
(p: { user_id: string }) => p.user_id === user.id,
)
if (alreadyJoined) redirect(`/rounds/${id}`)
// Round must be in lobby to join
if (round.status !== 'lobby') {
redirect(`/dashboard`)
}
return <JoinRound round={round} userId={user.id} />
}
+63
View File
@@ -0,0 +1,63 @@
import { createClient } from '@/lib/supabase/server'
import { redirect, notFound } from 'next/navigation'
import { RoundLobby } from '@/components/round/round-lobby'
export default async function RoundPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const supabase = await createClient()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const db = supabase as any
const {
data: { user },
} = await supabase.auth.getUser()
if (!user) redirect('/login')
const { data: round } = await db
.from('rounds')
.select(
`
id, name, date, status, holes_count, created_by,
course:courses (id, name, par),
tee:tees (id, name, color, course_rating, slope_rating)
`,
)
.eq('id', id)
.single()
if (!round) notFound()
const { data: players } = await db
.from('round_players')
.select(
`
id, role, handicap_index, course_handicap, joined_at,
profile:profiles (id, display_name, avatar_url)
`,
)
.eq('round_id', id)
.order('joined_at')
const { data: invites } = await db
.from('round_invites')
.select('id, email, accepted_at, created_at')
.eq('round_id', id)
.is('accepted_at', null)
.order('created_at')
const isCreator = round.created_by === user.id
if (round.status === 'active' || round.status === 'completed') {
redirect(`/rounds/${id}/scorecard`)
}
return (
<RoundLobby
round={round}
players={players ?? []}
pendingInvites={invites ?? []}
currentUserId={user.id}
isCreator={isCreator}
/>
)
}
+104
View File
@@ -0,0 +1,104 @@
import { createClient } from '@/lib/supabase/server'
import { redirect, notFound } from 'next/navigation'
import { ScorecardView } from '@/components/scorecard/scorecard-view'
export type Hole = {
id: string
hole_number: number
par: number
stroke_index: number
}
export type Player = {
user_id: string
course_handicap: number | null
handicap_index: number | null
profile: { id: string; display_name: string; avatar_url: string | null } | null
}
export type ScoreRow = {
hole_id: string
player_id: string
strokes: number
}
export type RoundInfo = {
id: string
name: string | null
date: string
status: string
holes_count: number
created_by: string
course: { name: string } | null
tee: { name: string; color: string } | null
}
export default async function ScorecardPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const supabase = await createClient()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const db = supabase as any
const {
data: { user },
} = await supabase.auth.getUser()
if (!user) redirect('/login')
const { data: round } = await db
.from('rounds')
.select(
`id, name, date, status, holes_count, created_by,
course:courses (name),
tee:tees (name, color)`,
)
.eq('id', id)
.single()
if (!round) notFound()
if (round.status === 'lobby') redirect(`/rounds/${id}`)
// Holes ordered by hole_number, limited to holes_count
const { data: holes } = await db
.from('holes')
.select('id, hole_number, par, stroke_index')
.eq('course_id', await getCourseId(db, id))
.order('hole_number')
.limit(round.holes_count)
const { data: players } = await db
.from('round_players')
.select(
`user_id, course_handicap, handicap_index,
profile:profiles (id, display_name, avatar_url)`,
)
.eq('round_id', id)
.order('joined_at')
const { data: scores } = await db
.from('scores')
.select('hole_id, player_id, strokes')
.eq('round_id', id)
return (
<ScorecardView
round={round as RoundInfo}
holes={(holes as Hole[]) ?? []}
players={(players as Player[]) ?? []}
initialScores={(scores as ScoreRow[]) ?? []}
currentUserId={user.id}
/>
)
}
async function getCourseId(db: any, roundId: string): Promise<string> {
const { data } = await db
.from('rounds')
.select('course_id')
.eq('id', roundId)
.single()
return data?.course_id ?? ''
}