restarting brackets
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { PreviewBracket } from "@/features/bracket/components/preview";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/_authed/admin/preview")({
|
||||
@@ -13,5 +12,5 @@ export const Route = createFileRoute("/_authed/admin/preview")({
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return <PreviewBracket />;
|
||||
return <p>Preview</p>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import { tournamentQueries } from '@/features/tournaments/queries'
|
||||
import { ensureServerQueryData } from '@/lib/tanstack-query/utils/ensure'
|
||||
import RunTournament from '@/features/tournaments/components/run-tournament'
|
||||
|
||||
export const Route = createFileRoute('/_authed/admin/tournaments/run/$id')({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
@@ -27,5 +26,5 @@ export const Route = createFileRoute('/_authed/admin/tournaments/run/$id')({
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams()
|
||||
return <RunTournament tournamentId={id} />
|
||||
return <p>Run tournament</p>
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ ensureSuperTokensBackend();
|
||||
const superTokensHandler = handleAuthAPIRequest();
|
||||
const handleRequest = async ({ request }: {request: Request}) => {
|
||||
console.log("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
|
||||
console.log("Handling auth request:", request);
|
||||
console.log("Handling auth request:", request.method, request.url);
|
||||
return superTokensHandler(request);
|
||||
};
|
||||
export const ServerRoute = createServerFileRoute('/api/auth/$').methods({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ScrollArea, Text } from "@mantine/core";
|
||||
import BracketView from "./bracket-view";
|
||||
import { Match } from "../types";
|
||||
import useAppShellHeight from "@/hooks/use-appshell-height";
|
||||
import { BracketMaps } from "../utils/bracket-maps";
|
||||
import { Match } from "@/features/matches/types";
|
||||
|
||||
interface BracketProps {
|
||||
winners: Match[][];
|
||||
72
src/features/_bracket/components/match-card.tsx
Normal file
72
src/features/_bracket/components/match-card.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ActionIcon, Card, Text } from '@mantine/core';
|
||||
import { PlayIcon } from '@phosphor-icons/react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { MatchSlot } from './match-slot';
|
||||
import { Match } from '../types';
|
||||
|
||||
interface MatchCardProps {
|
||||
match: Match;
|
||||
getParentMatchOrder: (parentLid: number) => number | string;
|
||||
onAnnounce?: (teamOne: any, teamTwo: any) => void;
|
||||
}
|
||||
|
||||
export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
match,
|
||||
getParentMatchOrder,
|
||||
onAnnounce
|
||||
}) => {
|
||||
|
||||
const showAnnounce = useMemo(() =>
|
||||
onAnnounce && match.home.team && match.away.team,
|
||||
[onAnnounce, match.home.team, match.away.team]);
|
||||
|
||||
const handleAnnounce = useCallback(() =>
|
||||
onAnnounce?.(match.home.team, match.away.team), [match.home.team, match.away.team]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
withBorder
|
||||
pos="relative"
|
||||
w={200}
|
||||
style={{ overflow: 'visible' }}
|
||||
data-match-lid={match.lid}
|
||||
>
|
||||
<Card.Section withBorder p={0}>
|
||||
<MatchSlot slot={match.home} getParentMatchOrder={getParentMatchOrder} />
|
||||
</Card.Section>
|
||||
|
||||
<Card.Section p={0} mb={-16}>
|
||||
<MatchSlot slot={match.away} getParentMatchOrder={getParentMatchOrder} />
|
||||
</Card.Section>
|
||||
|
||||
{match.reset && (
|
||||
<Text
|
||||
pos="absolute"
|
||||
top={-20}
|
||||
left={8}
|
||||
size="xs"
|
||||
c="dimmed"
|
||||
fw="bold"
|
||||
>
|
||||
* If necessary
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{showAnnounce && (
|
||||
<ActionIcon
|
||||
pos="absolute"
|
||||
variant="filled"
|
||||
color="green"
|
||||
top={-20}
|
||||
right={-12}
|
||||
onClick={handleAnnounce}
|
||||
bd="none"
|
||||
style={{ boxShadow: 'none' }}
|
||||
size="xs"
|
||||
>
|
||||
<PlayIcon size={12} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
51
src/features/_bracket/components/match-slot.tsx
Normal file
51
src/features/_bracket/components/match-slot.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Flex, Text } from "@mantine/core";
|
||||
import React from "react";
|
||||
import { SeedBadge } from "./seed-badge";
|
||||
|
||||
interface MatchSlotProps {
|
||||
slot: any;
|
||||
getParentMatchOrder: (parentLid: number) => number | string;
|
||||
}
|
||||
|
||||
export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
slot,
|
||||
getParentMatchOrder,
|
||||
}) => {
|
||||
const renderSlotContent = () => {
|
||||
if (slot?.seed) {
|
||||
return slot.team ? (
|
||||
<Text size="xs">{slot.team.name}</Text>
|
||||
) : (
|
||||
<Text size="xs" c="dimmed">
|
||||
Team {slot.seed}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (slot?.parent_lid !== null && slot?.parent_lid !== undefined) {
|
||||
return (
|
||||
<Text c="dimmed" size="xs">
|
||||
{slot.loser ? "Loser" : "Winner"} of Match{" "}
|
||||
{getParentMatchOrder(slot.parent_lid)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (slot) {
|
||||
return (
|
||||
<Text c="dimmed" size="xs" fs="italic">
|
||||
TBD
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex align="stretch">
|
||||
{slot?.seed && <SeedBadge seed={slot.seed} />}
|
||||
<div style={{ flex: 1, padding: "4px 8px" }}>{renderSlotContent()}</div>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
29
src/features/_bracket/components/seed-badge.tsx
Normal file
29
src/features/_bracket/components/seed-badge.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Text } from "@mantine/core";
|
||||
import React from "react";
|
||||
|
||||
interface SeedBadgeProps {
|
||||
seed: number;
|
||||
}
|
||||
|
||||
export const SeedBadge: React.FC<SeedBadgeProps> = ({ seed }) => {
|
||||
return (
|
||||
<Text
|
||||
size="xs"
|
||||
fw="bold"
|
||||
py="4"
|
||||
bg="var(--mantine-color-default-hover)"
|
||||
style={{
|
||||
width: "32px",
|
||||
textAlign: "center",
|
||||
color: "var(--mantine-color-text)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderTopLeftRadius: "var(--mantine-radius-default)",
|
||||
borderBottomLeftRadius: "var(--mantine-radius-default)",
|
||||
}}
|
||||
>
|
||||
{seed}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
48
src/features/_bracket/components/seed-list.tsx
Normal file
48
src/features/_bracket/components/seed-list.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Flex, Text, Select, Card } from "@mantine/core";
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface SeedListProps {
|
||||
teams: Team[];
|
||||
onSeedChange: (currentIndex: number, newIndex: number) => void;
|
||||
}
|
||||
|
||||
export function SeedList({ teams, onSeedChange }: SeedListProps) {
|
||||
const seedOptions = teams.map((_, index) => ({
|
||||
value: index.toString(),
|
||||
label: `Seed ${index + 1}`,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap={8}>
|
||||
{teams.map((team, index) => (
|
||||
<Card key={team.id} withBorder p="xs">
|
||||
<Flex align="center" gap="xs" justify="space-between">
|
||||
<Flex align="center" gap="xs">
|
||||
<Select
|
||||
value={index.toString()}
|
||||
data={seedOptions}
|
||||
onChange={(value) => {
|
||||
if (value !== null) {
|
||||
const newIndex = parseInt(value);
|
||||
if (newIndex !== index) {
|
||||
onSeedChange(index, newIndex);
|
||||
}
|
||||
}
|
||||
}}
|
||||
size="xs"
|
||||
w={100}
|
||||
/>
|
||||
<Text size="sm" fw={500}>
|
||||
{team.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
9
src/features/_bracket/components/styles.module.css
Normal file
9
src/features/_bracket/components/styles.module.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.bracket-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bracket-container {
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
18
src/features/_bracket/queries.ts
Normal file
18
src/features/_bracket/queries.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { previewBracket } from "./server";
|
||||
import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
||||
import { BracketData } from "./types";
|
||||
|
||||
const bracketKeys = {
|
||||
preview: (teams: number) => ["bracket", "preview", teams] as const,
|
||||
};
|
||||
|
||||
export const bracketQueries = {
|
||||
preview: (teams: number) => ({
|
||||
queryKey: bracketKeys.preview(teams),
|
||||
queryFn: () => previewBracket({ data: teams }),
|
||||
}),
|
||||
};
|
||||
|
||||
export const useBracketPreview = (teams: number) =>
|
||||
useServerSuspenseQuery<BracketData>(bracketQueries.preview(teams));
|
||||
21
src/features/_bracket/server.ts
Normal file
21
src/features/_bracket/server.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { superTokensFunctionMiddleware } from "@/utils/supertokens";
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import { z } from "zod";
|
||||
import { Logger } from "@/lib/logger";
|
||||
import brackets from "./utils";
|
||||
import { BracketData } from "./types";
|
||||
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
|
||||
|
||||
const logger = new Logger("Bracket Generation");
|
||||
|
||||
export const previewBracket = createServerFn()
|
||||
.validator(z.number())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: teams }) =>
|
||||
toServerResult(async () => {
|
||||
logger.info("Generating bracket", teams);
|
||||
if (!Object.keys(brackets).includes(teams.toString()))
|
||||
throw Error("Bracket not available");
|
||||
return brackets[teams as keyof typeof brackets] as BracketData;
|
||||
})
|
||||
);
|
||||
20
src/features/_bracket/types.ts
Normal file
20
src/features/_bracket/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*export interface Slot {
|
||||
seed?: number;
|
||||
team?: any;
|
||||
}
|
||||
|
||||
export interface Match {
|
||||
lid: number;
|
||||
round: number;
|
||||
order: number | null;
|
||||
type: string;
|
||||
home: Slot;
|
||||
away: Slot;
|
||||
reset?: boolean;
|
||||
}*/
|
||||
import { Match } from "../matches/types";
|
||||
|
||||
export interface BracketData {
|
||||
winners: Match[][];
|
||||
losers: Match[][];
|
||||
}
|
||||
24
src/features/_bracket/utils/index.ts
Normal file
24
src/features/_bracket/utils/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Imports saved json dumps of bracket generation from a python script that I didn't prioritize converting to TS
|
||||
*/
|
||||
import b12 from "../../../../scripts/brackets/12.json";
|
||||
import b13 from "../../../../scripts/brackets/13.json";
|
||||
import b14 from "../../../../scripts/brackets/14.json";
|
||||
import b15 from "../../../../scripts/brackets/15.json";
|
||||
import b16 from "../../../../scripts/brackets/16.json";
|
||||
import b17 from "../../../../scripts/brackets/17.json";
|
||||
import b18 from "../../../../scripts/brackets/18.json";
|
||||
import b19 from "../../../../scripts/brackets/19.json";
|
||||
import b20 from "../../../../scripts/brackets/20.json";
|
||||
|
||||
export default {
|
||||
12: b12,
|
||||
13: b13,
|
||||
14: b14,
|
||||
15: b15,
|
||||
16: b16,
|
||||
17: b17,
|
||||
18: b18,
|
||||
19: b19,
|
||||
20: b20,
|
||||
};
|
||||
@@ -12,33 +12,9 @@ export interface Match {
|
||||
away: Slot;
|
||||
reset?: boolean;
|
||||
}*/
|
||||
import { Match } from "../matches/types";
|
||||
|
||||
export interface BracketData {
|
||||
winners: Match[][];
|
||||
losers: Match[][];
|
||||
}
|
||||
|
||||
|
||||
export interface Match {
|
||||
id: number;
|
||||
order: number;
|
||||
lid: number;
|
||||
round: number;
|
||||
reset: boolean;
|
||||
home_cups?: number;
|
||||
away_cups?: number;
|
||||
ot_count?: number;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
bye?: boolean;
|
||||
home_from_lid?: number;
|
||||
away_from_lid?: number;
|
||||
home_from_loser?: boolean;
|
||||
away_from_loser?: boolean;
|
||||
is_losers_bracket?: boolean;
|
||||
tournament?: string;
|
||||
home?: string;
|
||||
away?: string;
|
||||
created?: Date;
|
||||
updated?: Date;
|
||||
}
|
||||
@@ -16,10 +16,10 @@ export interface Match {
|
||||
away_from_lid: number;
|
||||
home_from_loser: boolean;
|
||||
away_from_loser: boolean;
|
||||
is_losers_bracket: 'winners' | 'losers';
|
||||
tournament_id: string;
|
||||
home_id: string;
|
||||
away_id: string;
|
||||
is_losers_bracket: boolean;
|
||||
tournament: string;
|
||||
home: string;
|
||||
away: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
@@ -32,26 +32,18 @@ export const matchInputSchema = z.object({
|
||||
home_cups: z.number().int().min(0).optional().default(0),
|
||||
away_cups: z.number().int().min(0).optional().default(0),
|
||||
ot_count: z.number().int().min(0).optional().default(0),
|
||||
start_time: z.iso.datetime("Invalid start time format").optional(),
|
||||
end_time: z.iso.datetime("Invalid end time format").optional(),
|
||||
start_time: z.iso.datetime().optional(),
|
||||
end_time: z.iso.datetime().optional(),
|
||||
bye: z.boolean().optional().default(false),
|
||||
home_from_lid: z.number().int().min(1).optional(),
|
||||
away_from_lid: z.number().int().min(1).optional(),
|
||||
home_from_loser: z.boolean().optional().default(false),
|
||||
away_from_loser: z.boolean().optional().default(false),
|
||||
is_losers_bracket: z.boolean().optional().default(false),
|
||||
tournament_id: z.string().min(1),
|
||||
home_id: z.string().min(1).optional(),
|
||||
away_id: z.string().min(1).optional(),
|
||||
}).refine(
|
||||
(data) => {
|
||||
if (data.start_time && data.end_time) {
|
||||
return new Date(data.start_time) < new Date(data.end_time);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{ message: "End time must be after start time", path: ["end_time"] }
|
||||
);
|
||||
tournament: z.string().min(1),
|
||||
home: z.string().min(1).optional(),
|
||||
away: z.string().min(1).optional(),
|
||||
});
|
||||
|
||||
export type MatchInput = z.infer<typeof matchInputSchema>;
|
||||
export type MatchUpdateInput = Partial<MatchInput>;
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
import { useTournament } from '../queries'
|
||||
import { Box, Grid, NumberInput, Stack, Text, Title, Group, Flex, Divider, ScrollArea, Button } from '@mantine/core'
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import Avatar from '@/components/avatar'
|
||||
import { useBracketPreview } from '@/features/bracket/queries'
|
||||
import Bracket from '@/features/bracket/components/bracket'
|
||||
import { createBracketMaps, BracketMaps } from '@/features/bracket/utils/bracket-maps'
|
||||
import { Match, BracketData } from '@/features/bracket/types'
|
||||
|
||||
interface RunTournamentProps {
|
||||
tournamentId: string
|
||||
}
|
||||
|
||||
interface TeamWithSeed {
|
||||
id: string
|
||||
name: string
|
||||
seed: number
|
||||
}
|
||||
|
||||
const RunTournament = ({ tournamentId }: RunTournamentProps) => {
|
||||
const { data: tournament } = useTournament(tournamentId)
|
||||
const teamCount = tournament?.teams?.length || 0
|
||||
const { data: bracketData, isLoading } = useBracketPreview(teamCount)
|
||||
|
||||
const [teamSeeds, setTeamSeeds] = useState<Record<string, number>>(() => {
|
||||
if (!tournament?.teams) return {}
|
||||
return tournament.teams.reduce((acc, team, index) => {
|
||||
acc[team.id] = index + 1
|
||||
return acc
|
||||
}, {} as Record<string, number>)
|
||||
})
|
||||
|
||||
const [seededWinnersBracket, setSeededWinnersBracket] = useState<Match[][]>([])
|
||||
const [seededLosersBracket, setSeededLosersBracket] = useState<Match[][]>([])
|
||||
const [bracketMaps, setBracketMaps] = useState<BracketMaps | null>(null)
|
||||
|
||||
const sortedTeams = useMemo(() => {
|
||||
if (!tournament?.teams) return []
|
||||
|
||||
return tournament.teams
|
||||
.map(team => ({
|
||||
...team,
|
||||
seed: teamSeeds[team.id] || 1,
|
||||
}))
|
||||
.sort((a, b) => a.seed - b.seed)
|
||||
}, [tournament?.teams, teamSeeds])
|
||||
|
||||
const handleSeedChange = (teamId: string, newSeed: number) => {
|
||||
if (newSeed < 1 || !tournament?.teams) return
|
||||
|
||||
setTeamSeeds(prev => {
|
||||
const currSeed = prev[teamId]
|
||||
const newSeeds = { ...prev }
|
||||
|
||||
const otherTeams = tournament.teams!.filter(team => team.id !== teamId)
|
||||
|
||||
if (newSeed !== currSeed) {
|
||||
const currTeam = otherTeams.find(team => prev[team.id] === newSeed)
|
||||
|
||||
if (currTeam) {
|
||||
const affectedTeams = otherTeams.filter(team => {
|
||||
const teamSeed = prev[team.id]
|
||||
return newSeed < currSeed
|
||||
? teamSeed >= newSeed && teamSeed < currSeed
|
||||
: teamSeed > currSeed && teamSeed <= newSeed
|
||||
})
|
||||
|
||||
affectedTeams.forEach(team => {
|
||||
const teamSeed = prev[team.id]
|
||||
if (newSeed < currSeed) {
|
||||
newSeeds[team.id] = teamSeed + 1
|
||||
} else {
|
||||
newSeeds[team.id] = teamSeed - 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
newSeeds[teamId] = newSeed
|
||||
}
|
||||
|
||||
return newSeeds
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!bracketData || !tournament?.teams || sortedTeams.length === 0) return
|
||||
|
||||
const maps = createBracketMaps(bracketData)
|
||||
setBracketMaps(maps)
|
||||
|
||||
const mapBracket = (bracket: Match[][]) => {
|
||||
return bracket.map((round) =>
|
||||
round.map((match) => {
|
||||
const mappedMatch = { ...match }
|
||||
|
||||
if (match.home?.seed && match.home.seed > 0) {
|
||||
const team = sortedTeams.find(t => t.seed === match.home.seed)
|
||||
if (team) {
|
||||
mappedMatch.home = {
|
||||
...match.home,
|
||||
team: team,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match.away?.seed && match.away.seed > 0) {
|
||||
const team = sortedTeams.find(t => t.seed === match.away.seed)
|
||||
if (team) {
|
||||
mappedMatch.away = {
|
||||
...match.away,
|
||||
team: team,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mappedMatch
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
setSeededWinnersBracket(mapBracket(bracketData.winners))
|
||||
setSeededLosersBracket(mapBracket(bracketData.losers))
|
||||
}, [bracketData, sortedTeams])
|
||||
|
||||
if (!tournament) throw new Error('Tournament not found.')
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Grid.Col span={3}>
|
||||
<ScrollArea offsetScrollbars type='always' h={700}>
|
||||
<Title order={3}>Team Seeds</Title>
|
||||
{sortedTeams.map((team) => (
|
||||
<>
|
||||
<Group
|
||||
key={team.id}
|
||||
justify="space-between"
|
||||
p={4}
|
||||
>
|
||||
<Group gap="xs" style={{ flex: 1 }}>
|
||||
<Avatar size={24} name={team.name} />
|
||||
<Text fw={500} size="sm" truncate>{team.name}</Text>
|
||||
</Group>
|
||||
<NumberInput
|
||||
value={teamSeeds[team.id]}
|
||||
onChange={(value) => handleSeedChange(team.id, Number(value) || 1)}
|
||||
min={1}
|
||||
max={tournament.teams?.length || 1}
|
||||
size="xs"
|
||||
w={50}
|
||||
styles={{ input: { textAlign: 'center' } }}
|
||||
step={-1}
|
||||
/>
|
||||
</Group>
|
||||
<Divider />
|
||||
</>
|
||||
))}
|
||||
</ScrollArea>
|
||||
<Button fullWidth mt='md' onClick={() => console.log(sortedTeams)}>Start Tournament</Button>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={9}>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Tournament Bracket</Title>
|
||||
{isLoading ? (
|
||||
<Flex justify="center" align="center" h="400px">
|
||||
<Text c="dimmed">Loading bracket...</Text>
|
||||
</Flex>
|
||||
) : (
|
||||
<Bracket
|
||||
winners={seededWinnersBracket}
|
||||
losers={seededLosersBracket}
|
||||
bracketMaps={bracketMaps}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default RunTournament
|
||||
Reference in New Issue
Block a user