diff --git a/.nitro/types/nitro-config.d.ts b/.nitro/types/nitro-config.d.ts new file mode 100644 index 0000000..42e138f --- /dev/null +++ b/.nitro/types/nitro-config.d.ts @@ -0,0 +1,16 @@ +// Generated by nitro + +// App Config +import type { Defu } from 'defu' + + + +type UserAppConfig = Defu<{}, []> + +declare module "nitropack/types" { + interface AppConfig extends UserAppConfig {} + interface NitroRuntimeConfig { + + } +} +export {} \ No newline at end of file diff --git a/.nitro/types/nitro-imports.d.ts b/.nitro/types/nitro-imports.d.ts new file mode 100644 index 0000000..693da49 --- /dev/null +++ b/.nitro/types/nitro-imports.d.ts @@ -0,0 +1 @@ +export {} \ No newline at end of file diff --git a/.nitro/types/nitro-routes.d.ts b/.nitro/types/nitro-routes.d.ts new file mode 100644 index 0000000..6f9887a --- /dev/null +++ b/.nitro/types/nitro-routes.d.ts @@ -0,0 +1,8 @@ +// Generated by nitro +import type { Serialize, Simplify } from "nitropack/types"; +declare module "nitropack/types" { + type Awaited = T extends PromiseLike ? Awaited : T + interface InternalApi { + } +} +export {} \ No newline at end of file diff --git a/.nitro/types/nitro.d.ts b/.nitro/types/nitro.d.ts new file mode 100644 index 0000000..bf09bd4 --- /dev/null +++ b/.nitro/types/nitro.d.ts @@ -0,0 +1,3 @@ +/// +/// +/// \ No newline at end of file diff --git a/src/app/routes/_authed/admin/tournaments/run.$id.tsx b/src/app/routes/_authed/admin/tournaments/run.$id.tsx index 450e254..b3c91b1 100644 --- a/src/app/routes/_authed/admin/tournaments/run.$id.tsx +++ b/src/app/routes/_authed/admin/tournaments/run.$id.tsx @@ -39,30 +39,24 @@ function RouteComponent() { if (!tournament.matches || tournament.matches.length === 0) { return { winners: [], losers: [] } } - - console.log('Tournament Matches:', tournament.matches) const winnersMap = new Map() const losersMap = new Map() tournament.matches.sort((a, b) => a.lid - b.lid).forEach((match) => { - console.log('Processing Match:', match) if (!match.is_losers_bracket) { if (!winnersMap.has(match.round)) { winnersMap.set(match.round, []) } winnersMap.get(match.round)!.push(match) - console.log('Added to Winners Bracket:', match) } else { if (!losersMap.has(match.round)) { losersMap.set(match.round, []) } losersMap.get(match.round)!.push(match) - console.log('Added to Losers Bracket:', match) } }) - // Convert to dense arrays sorted by round (0, 1, 2, 3...) const winners = Array.from(winnersMap.entries()) .sort(([a], [b]) => a - b) .map(([, matches]) => matches) @@ -70,15 +64,10 @@ function RouteComponent() { const losers = Array.from(losersMap.entries()) .sort(([a], [b]) => a - b) .map(([, matches]) => matches) - - console.log('Winners Bracket (fixed):', winners) - console.log('Losers Bracket (fixed):', losers) return { winners, losers } }, [tournament.matches]) - console.log('Bracket Data:', bracket) - const handleSuccess = () => { router.navigate({ to: '/admin/tournaments/$id', @@ -86,13 +75,15 @@ function RouteComponent() { }) } - console.log('Tournament:', tournament) + const handleStartMatch = (match: Match) => { + + } return ( { tournament.matches?.length ? - + : ( { m={0} maw={fullWidth ? '100%' : 600} mx="auto" + pos="relative" {...props} > + {header.collapsed && header.withBackButton && ( + + )} + {header.collapsed && header.settingsLink && ( + + )} {children} ); diff --git a/src/components/swipeable-tabs.tsx b/src/components/swipeable-tabs.tsx index 78ada2c..023c84e 100644 --- a/src/components/swipeable-tabs.tsx +++ b/src/components/swipeable-tabs.tsx @@ -109,7 +109,7 @@ function SwipeableTabs({ ); return ( - + changeTab(index)} style={{ flex: 1, - padding: "var(--mantine-spacing-sm) var(--mantine-spacing-md)", + padding: "var(--mantine-spacing-sm) var(--mantine-spacing-xs)", textAlign: "center", color: activeTab === index @@ -155,7 +155,7 @@ function SwipeableTabs({ component="span" style={{ display: "inline-block", - paddingInline: "1rem", + paddingInline: "0.5rem", paddingBottom: "0.25rem", }} ref={setControlRef(index)} @@ -176,6 +176,7 @@ function SwipeableTabs({ overflow: "hidden", height: carouselHeight === "auto" ? "auto" : `${carouselHeight}px`, transition: "height 300ms ease", + touchAction: "pan-y", }} > {tabs.map((tab, index) => ( diff --git a/src/features/bracket/components/bracket-view.tsx b/src/features/bracket/components/bracket-view.tsx index 88b6bf7..468511d 100644 --- a/src/features/bracket/components/bracket-view.tsx +++ b/src/features/bracket/components/bracket-view.tsx @@ -1,16 +1,16 @@ -import React, { useCallback, useMemo } from "react"; +import React, { useMemo } from "react"; import { Text, ScrollArea } from "@mantine/core"; -import { MatchCard } from "./match-card"; import { BracketData } from "../types"; import { Bracket } from "./bracket"; import useAppShellHeight from "@/hooks/use-appshell-height"; +import { Match } from "@/features/matches/types"; interface BracketViewProps { bracket: BracketData; - onAnnounce?: (teamOne: any, teamTwo: any) => void; + onStartMatch?: (match: Match) => void; } -const BracketView: React.FC = ({ bracket, onAnnounce }) => { +const BracketView: React.FC = ({ bracket, onStartMatch }) => { const height = useAppShellHeight(); const orders = useMemo(() => { const map: Record = {}; @@ -32,14 +32,14 @@ const BracketView: React.FC = ({ bracket, onAnnounce }) => { Winners Bracket - + {bracket.losers && (
Losers Bracket - +
)} diff --git a/src/features/bracket/components/bracket.tsx b/src/features/bracket/components/bracket.tsx index 373ff17..bc9784c 100644 --- a/src/features/bracket/components/bracket.tsx +++ b/src/features/bracket/components/bracket.tsx @@ -5,13 +5,13 @@ import { MatchCard } from "./match-card"; interface BracketProps { rounds: Match[][]; orders: Record; - onAnnounce?: (teamOne: any, teamTwo: any) => void; + onStartMatch?: (match: Match) => void; } export const Bracket: React.FC = ({ rounds, orders, - onAnnounce, + onStartMatch, }) => { return ( @@ -32,7 +32,7 @@ export const Bracket: React.FC = ({ ); diff --git a/src/features/bracket/components/match-card.tsx b/src/features/bracket/components/match-card.tsx index c8f7a2a..ef6b37b 100644 --- a/src/features/bracket/components/match-card.tsx +++ b/src/features/bracket/components/match-card.tsx @@ -7,13 +7,13 @@ import { Match } from "@/features/matches/types"; interface MatchCardProps { match: Match; orders: Record; - onAnnounce?: (teamOne: any, teamTwo: any) => void; + onStartMatch?: (match: Match) => void; } export const MatchCard: React.FC = ({ match, orders, - onAnnounce, + onStartMatch, }) => { const homeSlot = useMemo( () => ({ @@ -35,13 +35,13 @@ export const MatchCard: React.FC = ({ ); const showToolbar = useMemo( - () => match.home && match.away && onAnnounce, + () => match.home && match.away && onStartMatch, [match.home, match.away] ); const handleAnnounce = useCallback( - () => onAnnounce?.(match.home, match.away), - [onAnnounce, match.home, match.away] + () => onStartMatch?.(match), + [onStartMatch, match] ); const handleEdit = useCallback(() => { diff --git a/src/features/core/components/back-button.tsx b/src/features/core/components/back-button.tsx index f2783d6..6b09ad6 100644 --- a/src/features/core/components/back-button.tsx +++ b/src/features/core/components/back-button.tsx @@ -11,11 +11,11 @@ const BackButton = ({ offsetY }: BackButtonProps) => { return ( router.history.back()} pos='absolute' - left={{ base: 0, sm: 100, md: 200, lg: 300 }} - m={20} + left={16} + top={0} > diff --git a/src/features/core/components/header.tsx b/src/features/core/components/header.tsx index c6b2374..9cf8167 100644 --- a/src/features/core/components/header.tsx +++ b/src/features/core/components/header.tsx @@ -13,15 +13,11 @@ const Header = ({ withBackButton, settingsLink, collapsed, title, scrollPosition }, [collapsed, scrollPosition.y]); return ( - <> - {settingsLink && } - {withBackButton && } - - - {title} - - - + + + {title} + + ); } diff --git a/src/features/core/components/layout.tsx b/src/features/core/components/layout.tsx index ddc0ae3..fec97ae 100644 --- a/src/features/core/components/layout.tsx +++ b/src/features/core/components/layout.tsx @@ -40,7 +40,8 @@ const Layout: React.FC = ({ children }) => { mah='100%' pb={{ base: 70, md: 0 }} px={{ base: 0.01, sm: 100, md: 200, lg: 300 }} - style={{ transition: 'none' }} + maw='100dvw' + style={{ transition: 'none', overflow: 'hidden' }} > diff --git a/src/features/core/components/settings-button.tsx b/src/features/core/components/settings-button.tsx index 517b96d..08f4431 100644 --- a/src/features/core/components/settings-button.tsx +++ b/src/features/core/components/settings-button.tsx @@ -12,11 +12,11 @@ const SettingsButton = ({ offsetY, to }: SettingButtonProps) => { return ( navigate({ to })} pos='absolute' - right={{ base: 0, sm: 100, md: 200, lg: 300 }} - m={20} + right={16} + top={0} > diff --git a/src/features/login/components/layout.tsx b/src/features/login/components/layout.tsx index 0092b40..e4c313f 100644 --- a/src/features/login/components/layout.tsx +++ b/src/features/login/components/layout.tsx @@ -31,8 +31,8 @@ const Layout: React.FC = ({ children }) => { radius='md' > - - Welcome to FLXN + + Welcome to FLXN {children} diff --git a/src/features/matches/server.ts b/src/features/matches/server.ts index 909cd4a..798e3b9 100644 --- a/src/features/matches/server.ts +++ b/src/features/matches/server.ts @@ -19,18 +19,15 @@ export const generateTournamentBracket = createServerFn() toServerResult(async () => { logger.info('Generating tournament bracket', { tournamentId, teamCount: orderedTeamIds.length }); - // Get tournament with teams const tournament = await pbAdmin.getTournament(tournamentId); if (!tournament) { throw new Error('Tournament not found'); } - // Check if tournament already has matches if (tournament.matches && tournament.matches.length > 0) { throw new Error('Tournament already has matches generated'); } - // Get bracket template based on team count const teamCount = orderedTeamIds.length; if (!Object.keys(brackets).includes(teamCount.toString())) { throw new Error(`Bracket not available for ${teamCount} teams`); @@ -38,16 +35,13 @@ export const generateTournamentBracket = createServerFn() const bracketTemplate = brackets[teamCount as keyof typeof brackets] as any; - // Create seed to team mapping (index + 1 = seed) const seedToTeamId = new Map(); orderedTeamIds.forEach((teamId, index) => { seedToTeamId.set(index + 1, teamId); }); - // Convert bracket template to match records const matchInputs: MatchInput[] = []; - // Process winners bracket bracketTemplate.winners.forEach((round: any[]) => { round.forEach((match: any) => { const matchInput: MatchInput = { @@ -67,7 +61,6 @@ export const generateTournamentBracket = createServerFn() tournament: tournamentId, }; - // Assign teams based on seeds if (match.home_seed) { const teamId = seedToTeamId.get(match.home_seed); if (teamId) { @@ -88,7 +81,6 @@ export const generateTournamentBracket = createServerFn() }); }); - // Process losers bracket bracketTemplate.losers.forEach((round: any[]) => { round.forEach((match: any) => { const matchInput: MatchInput = { @@ -108,17 +100,12 @@ export const generateTournamentBracket = createServerFn() tournament: tournamentId, }; - // Losers bracket matches don't start with teams - // Teams come from winners bracket losses - matchInputs.push(matchInput); }); }); - // Create all matches const createdMatches = await pbAdmin.createMatches(matchInputs); - // Update tournament to include all match IDs in the matches relation const matchIds = createdMatches.map(match => match.id); await pbAdmin.updateTournamentMatches(tournamentId, matchIds); @@ -133,4 +120,22 @@ export const generateTournamentBracket = createServerFn() matches: createdMatches, }; }) - ); \ No newline at end of file + ); + +export const startMatch = createServerFn() + .validator(z.string()) + .middleware([superTokensAdminFunctionMiddleware]) + .handler(async ({ data }) => + toServerResult(async () => { + logger.info('Starting match', data); + + let match = await pbAdmin.getMatch(data); + if (!match) { + throw new Error('Match not found'); + } + + match = await pbAdmin.updateMatch(data, { + start_time: new Date().toISOString() + }) + } + )); \ No newline at end of file diff --git a/src/features/tournaments/components/profile/index.tsx b/src/features/tournaments/components/profile/index.tsx index 72e1017..0afd237 100644 --- a/src/features/tournaments/components/profile/index.tsx +++ b/src/features/tournaments/components/profile/index.tsx @@ -25,6 +25,9 @@ const Profile = ({ id }: ProfileProps) => { label: "Teams", content: <> + + + } ]; diff --git a/src/lib/pocketbase/services/matches.ts b/src/lib/pocketbase/services/matches.ts index 172b88b..7126cdf 100644 --- a/src/lib/pocketbase/services/matches.ts +++ b/src/lib/pocketbase/services/matches.ts @@ -1,9 +1,18 @@ import { logger } from "@/lib/logger"; import type { Match, MatchInput } from "@/features/matches/types"; import type PocketBase from "pocketbase"; +import { transformMatch } from "../util/transform-types"; export function createMatchesService(pb: PocketBase) { return { + async getMatch(id: string): Promise { + logger.info("PocketBase | Getting match", id); + const result = await pb.collection("matches").getOne(id, { + expand: "tournament, home, away", + }); + return transformMatch(result); + }, + async createMatch(data: MatchInput): Promise { logger.info("PocketBase | Creating match", data); const result = await pb.collection("matches").create(data); @@ -11,9 +20,11 @@ export function createMatchesService(pb: PocketBase) { }, async createMatches(matches: MatchInput[]): Promise { - logger.info("PocketBase | Creating multiple matches", { count: matches.length }); + logger.info("PocketBase | Creating multiple matches", { + count: matches.length, + }); const results = await Promise.all( - matches.map(match => pb.collection("matches").create(match)) + matches.map((match) => pb.collection("matches").create(match)) ); return results; }, @@ -30,10 +41,10 @@ export function createMatchesService(pb: PocketBase) { filter: `tournament = "${tournamentId}"`, fields: "id", }); - + await Promise.all( - matches.map(match => pb.collection("matches").delete(match.id)) + matches.map((match) => pb.collection("matches").delete(match.id)) ); }, }; -} \ No newline at end of file +}