This commit is contained in:
yohlo
2025-08-20 22:35:40 -05:00
commit f51c278cd3
169 changed files with 8173 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
import { Badge, Card, Text, Image, Stack, Flex } from "@mantine/core"
import { Tournament } from "@/features/tournaments/types"
import { useMemo } from "react"
import { CaretRightIcon } from "@phosphor-icons/react"
import { useNavigate } from "@tanstack/react-router"
interface TournamentCardProps {
tournament: Tournament
}
export const TournamentCard = ({ tournament }: TournamentCardProps) => {
const navigate = useNavigate({ from: '/tournaments/$tournamentId' })
const date = useMemo(() => new Date(tournament.start_time), [tournament?.start_time])
const year = useMemo(() => date.getFullYear(), [date])
const month = useMemo(() => date.getMonth(), [date])
const monthName = useMemo(() => new Date(date.getFullYear(), month, 1).toLocaleString('default', { month: 'long' }), [date])
const day = useMemo(() => date.getDate(), [date])
return (
<Card shadow="sm" padding="lg" radius="md" withBorder style={{ cursor: 'pointer' }} onClick={() => navigate({ to: `/tournaments/${tournament.id}` })}>
<Stack>
<Flex align='center' gap='md'>
<Image
src={tournament.logo_url}
maw={100}
mah={100}
fit='contain'
alt={tournament.name}
/>
<Stack ta='center' mx='auto' gap='0'>
<Text size='lg' fw={800}>{tournament.name} <CaretRightIcon size={12} weight='bold' /></Text>
<Text c='dimmed' size='xs' fw={600}>{monthName} {day}, {year}</Text>
<Stack gap={4} mt={4}>
{ /* TODO: Add medalists when data is available */}
<Badge variant='dot' color='gold'>Longer Team Name Goes Here</Badge>
<Badge variant='dot' color='silver'>Some Team</Badge>
<Badge variant='dot' color='orange'>Medium Team Name</Badge>
</Stack>
</Stack>
</Flex>
</Stack>
</Card>
)
}

View File

@@ -0,0 +1,3 @@
import { Logger } from "@/lib/logger";
export const logger = new Logger('Tournaments');

View File

@@ -0,0 +1,18 @@
import { queryOptions, useQuery } from "@tanstack/react-query";
import { getTournament, listTournaments } from "./server";
const tournamentKeys = {
list: ['tournaments'] as const,
details: (id: string) => [...tournamentKeys.list, id] as const,
};
export const tournamentQueries = {
list: () => queryOptions({
queryKey: tournamentKeys.list,
queryFn: listTournaments,
}),
details: (id: string) => queryOptions({
queryKey: tournamentKeys.details(id),
queryFn: () => getTournament({ data: id }),
}),
};

View File

@@ -0,0 +1,43 @@
import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { tournamentInputSchema } from "@/features/tournaments/types";
import { logger } from ".";
import { z } from "zod";
export const listTournaments = createServerFn()
.middleware([superTokensFunctionMiddleware])
.handler(async () => {
try {
const result = await pbAdmin.listTournaments();
return result;
} catch (error) {
logger.error('Error fetching tournaments', error);
return [];
}
});
export const createTournament = createServerFn()
.validator(tournamentInputSchema)
.middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data }) => {
try {
logger.info('Creating tournament', data);
const tournament = await pbAdmin.createTournament(data);
return tournament;
} catch (error) {
logger.error('Error creating tournament', error);
return null;
}
});
export const getTournament = createServerFn()
.validator(z.string())
.middleware([superTokensFunctionMiddleware])
.handler(async ({ data: tournamentId }) => {
logger.info('Getting tournament', tournamentId);
const tournament = await pbAdmin.getTournament(tournamentId);
return tournament;
});

View File

@@ -0,0 +1,45 @@
import { Team } from "@/features/teams/types";
import { z } from "zod";
export interface Tournament {
id: string;
name: string;
location?: string;
desc?: string;
rules?: string;
logo_url?: string;
enroll_time?: string;
start_time: string;
end_time?: string;
created: string;
updated: string;
teams?: Team[];
}
// Schema for the form (client-side)
export const tournamentFormSchema = z.object({
name: z.string(),
location: z.string().optional(),
desc: z.string().optional(),
rules: z.string().optional(),
logo_url: z.string().optional(),
enroll_time: z.string(),
start_time: z.string(),
end_time: z.string().optional(),
});
// Schema for the server input (with base64 logo)
export const tournamentInputSchema = z.object({
name: z.string(),
location: z.string().optional(),
desc: z.string().optional(),
rules: z.string().optional(),
logo_url: z.string().optional(),
enroll_time: z.string(),
start_time: z.string(),
end_time: z.string().optional(),
});
export type TournamentFormInput = z.infer<typeof tournamentFormSchema>;
export type TournamentInput = z.infer<typeof tournamentInputSchema>;
export type TournamentUpdateInput = Partial<TournamentInput>;