tournament logo upload via api

This commit is contained in:
yohlo
2025-08-24 13:50:48 -05:00
parent 466a3365f0
commit 936ab0ce72
13 changed files with 417 additions and 23 deletions

View File

@@ -1,4 +1,4 @@
import { Stack, TextInput, Textarea } from "@mantine/core";
import { FileInput, Stack, TextInput, Textarea } from "@mantine/core";
import { useForm, UseFormInput } from "@mantine/form";
import { LinkIcon } from "@phosphor-icons/react";
import SlidePanel, { SlidePanelField } from "@/components/sheet/slide-panel";
@@ -6,6 +6,10 @@ import { TournamentFormInput } from "@/features/tournaments/types";
import { DateTimePicker } from "./date-time-picker";
import { isNotEmpty } from "@mantine/form";
import useCreateTournament from "../hooks/use-create-tournament";
import toast from '@/lib/sonner';
import { logger } from "..";
import { useQueryClient } from "@tanstack/react-query";
import { tournamentQueries } from "@/features/tournaments/queries";
const CreateTournament = ({ close }: { close: () => void }) => {
@@ -14,7 +18,6 @@ const CreateTournament = ({ close }: { close: () => void }) => {
name: 'Test Tournament',
location: 'Test Location',
desc: 'Test Description',
logo_url: 'https://en.wikipedia.org/wiki/Trophy#/media/File:1934_Melbourne_Cup,_National_Museum_of_Australia.jpg',
start_time: '2025-01-01T00:00:00Z',
enroll_time: '2025-01-01T00:00:00Z',
},
@@ -28,12 +31,45 @@ const CreateTournament = ({ close }: { close: () => void }) => {
}
const form = useForm(config);
const queryClient = useQueryClient();
const { mutate: createTournament, isPending } = useCreateTournament();
const handleSubmit = async (values: TournamentFormInput) => {
createTournament(values, {
onSuccess: () => {
const { logo, ...tournamentData } = values;
createTournament(tournamentData, {
onSuccess: async (tournament) => {
if (logo && tournament) {
try {
const formData = new FormData();
formData.append('tournamentId', tournament.id);
formData.append('logo', logo);
const response = await fetch('/api/tournaments/upload-logo', {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to upload logo');
}
const result = await response.json();
queryClient.invalidateQueries({ queryKey: tournamentQueries.list().queryKey });
queryClient.setQueryData(
tournamentQueries.details(result.tournament!.id).queryKey,
result.tournament
);
toast.success('Tournament created successfully!');
} catch (error: any) {
toast.error(`Tournament created but logo upload failed: ${error.message}`);
logger.error('Tournament logo upload error', error);
}
}
close();
}
});
@@ -67,12 +103,12 @@ const CreateTournament = ({ close }: { close: () => void }) => {
{...form.getInputProps('desc')}
/>
<TextInput
key={form.key('logo_url')}
accept="image/*"
<FileInput
key={form.key('logo')}
accept="image/png,image/jpeg,image/gif,image/jpg"
label="Logo"
leftSection={<LinkIcon size={16} />}
{...form.getInputProps('logo_url')}
{...form.getInputProps('logo')}
/>
<SlidePanelField

View File

@@ -18,7 +18,6 @@ const useCreateTournament = () => {
toast.error('There was an issue creating your tournament. Please try again later.');
logger.error('Error creating tournament', data);
} else {
toast.success('Tournament created successfully!');
logger.info('Tournament created successfully', data);
navigate({ to: '/tournaments' });
}

View File

@@ -13,7 +13,7 @@ const Profile = ({ player }: ProfileProps) => {
const tabs = [
{
label: "Overview",
content: <Text p="md">Panel 1 content</Text>
content: <Text p="md">Stats/Badges will go here</Text>
},
{
label: "Teams",

View File

@@ -4,7 +4,7 @@ import { z } from 'zod';
export interface Team {
id: string;
name: string;
logo_url: string;
logo: string;
primary_color: string;
accent_color: string;
song_id: string;
@@ -22,7 +22,7 @@ export interface Team {
export const teamInputSchema = z.object({
name: z.string().min(1, "Team name is required").max(100, "Name too long"),
logo_url: z.url("Invalid logo URL").optional(),
logo: z.file("Invalid logo").optional(),
primary_color: z.string().regex(/^#[0-9A-F]{6}$/i, "Must be valid hex color (#FF0000)").optional(),
accent_color: z.string().regex(/^#[0-9A-F]{6}$/i, "Must be valid hex color (#FF0000)").optional(),
song_id: z.string().max(255).optional(),

View File

@@ -1,7 +1,7 @@
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 { CaretRightIcon, TrophyIcon } from "@phosphor-icons/react"
import { useNavigate } from "@tanstack/react-router"
interface TournamentCardProps {
@@ -26,11 +26,12 @@ export const TournamentCard = ({ tournament }: TournamentCardProps) => {
<Stack>
<Flex align='center' gap='md'>
<Image
src={tournament.logo_url}
src={tournament.logo ? `/api/files/tournaments/${tournament.id}/${tournament.logo}` : undefined}
maw={100}
mah={100}
fit='contain'
alt={tournament.name}
fallbackSrc={"TODO"}
/>
<Stack ta='center' mx='auto' gap='0'>
<Text size='lg' fw={800}>{tournament.name} <CaretRightIcon size={12} weight='bold' /></Text>

View File

@@ -7,7 +7,7 @@ export interface Tournament {
location?: string;
desc?: string;
rules?: string;
logo_url?: string;
logo?: string;
enroll_time?: string;
start_time: string;
end_time?: string;
@@ -22,7 +22,7 @@ export const tournamentFormSchema = z.object({
location: z.string().optional(),
desc: z.string().optional(),
rules: z.string().optional(),
logo_url: z.string().optional(),
logo: z.file().optional(),
enroll_time: z.string(),
start_time: z.string(),
end_time: z.string().optional(),
@@ -34,7 +34,7 @@ export const tournamentInputSchema = z.object({
location: z.string().optional(),
desc: z.string().optional(),
rules: z.string().optional(),
logo_url: z.string().optional(),
logo: z.file().optional(),
enroll_time: z.string(),
start_time: z.string(),
end_time: z.string().optional(),