significant refactor
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { Autocomplete, Stack, ActionIcon, Text, Group, Loader } from "@mantine/core";
|
||||
import {
|
||||
Autocomplete,
|
||||
Stack,
|
||||
ActionIcon,
|
||||
Text,
|
||||
Group,
|
||||
Loader,
|
||||
} from "@mantine/core";
|
||||
import { TrashIcon } from "@phosphor-icons/react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState, useCallback, useMemo, memo } from "react";
|
||||
import { tournamentQueries } from "../queries";
|
||||
import { useTournament, useUnenrolledTeams } from "../queries";
|
||||
import useEnrollTeam from "../hooks/use-enroll-team";
|
||||
import useUnenrollTeam from "../hooks/use-unenroll-team";
|
||||
import Avatar from "@/components/avatar";
|
||||
@@ -12,13 +18,17 @@ interface EditEnrolledTeamsProps {
|
||||
tournamentId: string;
|
||||
}
|
||||
|
||||
const TeamItem = memo(({ team, onUnenroll, disabled }: {
|
||||
interface TeamItemProps {
|
||||
team: Team;
|
||||
onUnenroll: (teamId: string) => void;
|
||||
disabled: boolean;
|
||||
}) => {
|
||||
const playerNames = useMemo(() =>
|
||||
team.players?.map(p => `${p.first_name} ${p.last_name}`).join(", ") || "",
|
||||
}
|
||||
|
||||
const TeamItem = memo(({ team, onUnenroll, disabled }: TeamItemProps) => {
|
||||
const playerNames = useMemo(
|
||||
() =>
|
||||
team.players?.map((p) => `${p.first_name} ${p.last_name}`).join(", ") ||
|
||||
"",
|
||||
[team.players]
|
||||
);
|
||||
|
||||
@@ -26,9 +36,13 @@ const TeamItem = memo(({ team, onUnenroll, disabled }: {
|
||||
<Group py="xs" px="sm" w="100%" gap="sm" align="center">
|
||||
<Avatar size={32} name={team.name} />
|
||||
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} truncate>{team.name}</Text>
|
||||
<Text fw={500} truncate>
|
||||
{team.name}
|
||||
</Text>
|
||||
{playerNames && (
|
||||
<Text size="xs" c="dimmed" truncate>{playerNames}</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{playerNames}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
<ActionIcon
|
||||
@@ -47,30 +61,43 @@ const TeamItem = memo(({ team, onUnenroll, disabled }: {
|
||||
const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { data: tournament, isLoading: tournamentLoading } =
|
||||
useQuery(tournamentQueries.details(tournamentId));
|
||||
const { data: unenrolledTeams = [], isLoading: unenrolledLoading } =
|
||||
useQuery(tournamentQueries.unenrolled(tournamentId));
|
||||
const { data: tournament, isLoading: tournamentLoading } =
|
||||
useTournament(tournamentId);
|
||||
const { data: unenrolledTeams = [], isLoading: unenrolledLoading } =
|
||||
useUnenrolledTeams(tournamentId);
|
||||
|
||||
const { mutate: enrollTeam, isPending: isEnrolling } = useEnrollTeam();
|
||||
const { mutate: unenrollTeam, isPending: isUnenrolling } = useUnenrollTeam();
|
||||
|
||||
const autocompleteData = useMemo(() =>
|
||||
unenrolledTeams.map((team: Team) => ({ value: team.id, label: team.name })),
|
||||
const autocompleteData = useMemo(
|
||||
() =>
|
||||
unenrolledTeams.map((team: Team) => ({
|
||||
value: team.id,
|
||||
label: team.name,
|
||||
})),
|
||||
[unenrolledTeams]
|
||||
);
|
||||
|
||||
const handleEnrollTeam = useCallback((teamId: string) => {
|
||||
enrollTeam({ tournamentId, teamId }, {
|
||||
onSuccess: () => {
|
||||
setSearch("");
|
||||
}
|
||||
});
|
||||
}, [enrollTeam, tournamentId, setSearch]);
|
||||
const handleEnrollTeam = useCallback(
|
||||
(teamId: string) => {
|
||||
enrollTeam(
|
||||
{ tournamentId, teamId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSearch("");
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
[enrollTeam, tournamentId, setSearch]
|
||||
);
|
||||
|
||||
const handleUnenrollTeam = useCallback((teamId: string) => {
|
||||
unenrollTeam({ tournamentId, teamId });
|
||||
}, [unenrollTeam, tournamentId]);
|
||||
const handleUnenrollTeam = useCallback(
|
||||
(teamId: string) => {
|
||||
unenrollTeam({ tournamentId, teamId });
|
||||
},
|
||||
[unenrollTeam, tournamentId]
|
||||
);
|
||||
|
||||
const isLoading = tournamentLoading || unenrolledLoading;
|
||||
const enrolledTeams = tournament?.teams || [];
|
||||
@@ -79,7 +106,9 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
return (
|
||||
<Stack gap="lg" w="100%">
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} size="sm">Add Team</Text>
|
||||
<Text fw={600} size="sm">
|
||||
Add Team
|
||||
</Text>
|
||||
<Autocomplete
|
||||
placeholder="Search for teams to enroll..."
|
||||
data={autocompleteData}
|
||||
@@ -95,18 +124,26 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text fw={600} size="sm">Enrolled Teams</Text>
|
||||
<Text size="xs" c="dimmed">{enrolledTeams.length} teams</Text>
|
||||
<Text fw={600} size="sm">
|
||||
Enrolled Teams
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{enrolledTeams.length} teams
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="md"><Loader size="sm" /></Group>
|
||||
<Group justify="center" py="md">
|
||||
<Loader size="sm" />
|
||||
</Group>
|
||||
) : !hasEnrolledTeams ? (
|
||||
<Text size="sm" c="dimmed" ta="center" py="lg">No teams enrolled yet</Text>
|
||||
<Text size="sm" c="dimmed" ta="center" py="lg">
|
||||
No teams enrolled yet
|
||||
</Text>
|
||||
) : (
|
||||
<Stack gap="xs" w="100%">
|
||||
{enrolledTeams.map((team: Team) => (
|
||||
<TeamItem
|
||||
<TeamItem
|
||||
key={team.id}
|
||||
team={team}
|
||||
onUnenroll={handleUnenrollTeam}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { tournamentQueries } from "../queries";
|
||||
import { useTournament } from "../queries";
|
||||
import { List } from "@mantine/core";
|
||||
import ListButton from "@/components/list-button";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import TournamentForm from "./tournament-form";
|
||||
import { HardDrivesIcon, PencilLineIcon, UsersThreeIcon } from "@phosphor-icons/react";
|
||||
import {
|
||||
HardDrivesIcon,
|
||||
PencilLineIcon,
|
||||
UsersThreeIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import EditEnrolledTeams from "./edit-enrolled-teams";
|
||||
|
||||
@@ -13,26 +16,52 @@ interface ManageTournamentProps {
|
||||
}
|
||||
|
||||
const ManageTournament = ({ tournamentId }: ManageTournamentProps) => {
|
||||
const { data: tournament } = useSuspenseQuery(
|
||||
tournamentQueries.details(tournamentId)
|
||||
);
|
||||
const { data: tournament } = useTournament(tournamentId);
|
||||
|
||||
if (!tournament) throw new Error("Tournament not found.");
|
||||
const { isOpen: editTournamentOpened, open: openEditTournament, close: closeEditTournament } = useSheet();
|
||||
const { isOpen: editRulesOpened, open: openEditRules, close: closeEditRules } = useSheet();
|
||||
const { isOpen: editTeamsOpened, open: openEditTeams, close: closeEditTeams } = useSheet();
|
||||
const {
|
||||
isOpen: editTournamentOpened,
|
||||
open: openEditTournament,
|
||||
close: closeEditTournament,
|
||||
} = useSheet();
|
||||
const {
|
||||
isOpen: editRulesOpened,
|
||||
open: openEditRules,
|
||||
close: closeEditRules,
|
||||
} = useSheet();
|
||||
const {
|
||||
isOpen: editTeamsOpened,
|
||||
open: openEditTeams,
|
||||
close: closeEditTeams,
|
||||
} = useSheet();
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
<ListButton label="Edit Tournament" Icon={HardDrivesIcon} onClick={openEditTournament} />
|
||||
<ListButton label="Edit Rules" Icon={PencilLineIcon} onClick={openEditRules} />
|
||||
<ListButton label="Edit Enrolled Teams" Icon={UsersThreeIcon} onClick={openEditTeams} />
|
||||
<ListButton
|
||||
label="Edit Tournament"
|
||||
Icon={HardDrivesIcon}
|
||||
onClick={openEditTournament}
|
||||
/>
|
||||
<ListButton
|
||||
label="Edit Rules"
|
||||
Icon={PencilLineIcon}
|
||||
onClick={openEditRules}
|
||||
/>
|
||||
<ListButton
|
||||
label="Edit Enrolled Teams"
|
||||
Icon={UsersThreeIcon}
|
||||
onClick={openEditTeams}
|
||||
/>
|
||||
</List>
|
||||
|
||||
<Sheet title="Edit Tournament" opened={editTournamentOpened} onChange={closeEditTournament}>
|
||||
<TournamentForm
|
||||
tournamentId={tournament.id}
|
||||
<Sheet
|
||||
title="Edit Tournament"
|
||||
opened={editTournamentOpened}
|
||||
onChange={closeEditTournament}
|
||||
>
|
||||
<TournamentForm
|
||||
tournamentId={tournament.id}
|
||||
initialValues={{
|
||||
name: tournament.name,
|
||||
location: tournament.location,
|
||||
@@ -40,20 +69,28 @@ const ManageTournament = ({ tournamentId }: ManageTournamentProps) => {
|
||||
start_time: tournament.start_time,
|
||||
enroll_time: tournament.enroll_time,
|
||||
end_time: tournament.end_time,
|
||||
}}
|
||||
close={closeEditTournament}
|
||||
}}
|
||||
close={closeEditTournament}
|
||||
/>
|
||||
</Sheet>
|
||||
|
||||
<Sheet title="Edit Rules" opened={editRulesOpened} onChange={closeEditRules}>
|
||||
<Sheet
|
||||
title="Edit Rules"
|
||||
opened={editRulesOpened}
|
||||
onChange={closeEditRules}
|
||||
>
|
||||
<p>Test</p>
|
||||
</Sheet>
|
||||
|
||||
<Sheet title="Edit Enrolled Teams" opened={editTeamsOpened} onChange={closeEditTeams}>
|
||||
<Sheet
|
||||
title="Edit Enrolled Teams"
|
||||
opened={editTeamsOpened}
|
||||
onChange={closeEditTeams}
|
||||
>
|
||||
<EditEnrolledTeams tournamentId={tournamentId} />
|
||||
</Sheet>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageTournament;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Box, Divider, Text } from "@mantine/core";
|
||||
import { Box, Text } from "@mantine/core";
|
||||
import Header from "./header";
|
||||
import TeamList from "@/features/teams/components/team-list";
|
||||
import SwipeableTabs from "@/components/swipeable-tabs";
|
||||
import { Tournament } from "../../types";
|
||||
import { PreviewBracket } from "@/features/bracket/components/preview";
|
||||
import { useTournament } from "../../queries";
|
||||
|
||||
interface ProfileProps {
|
||||
tournament: Tournament;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const Profile = ({ tournament }: ProfileProps) => {
|
||||
const Profile = ({ id }: ProfileProps) => {
|
||||
const { data: tournament } = useTournament(id);
|
||||
if (!tournament) return null;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Overview",
|
||||
|
||||
@@ -1,50 +1,73 @@
|
||||
import { Badge, Card, Text, Image, Stack, Flex } from "@mantine/core"
|
||||
import { Tournament } from "@/features/tournaments/types"
|
||||
import { useMemo } from "react"
|
||||
import { CaretRightIcon, TrophyIcon } from "@phosphor-icons/react"
|
||||
import { useNavigate } from "@tanstack/react-router"
|
||||
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
|
||||
tournament: Tournament;
|
||||
}
|
||||
|
||||
export const TournamentCard = ({ tournament }: TournamentCardProps) => {
|
||||
const navigate = useNavigate({ from: '/tournaments/$tournamentId' })
|
||||
const navigate = useNavigate();
|
||||
const displayDate = useMemo(() => {
|
||||
if (!tournament.start_time) return null
|
||||
const date = new Date(tournament.start_time)
|
||||
if (isNaN(date.getTime())) return null
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
}, [tournament.start_time])
|
||||
if (!tournament.start_time) return null;
|
||||
const date = new Date(tournament.start_time);
|
||||
if (isNaN(date.getTime())) return null;
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}, [tournament.start_time]);
|
||||
|
||||
return (
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder style={{ cursor: 'pointer' }} onClick={() => navigate({ to: `/tournaments/${tournament.id}` })}>
|
||||
<Card
|
||||
shadow="sm"
|
||||
padding="lg"
|
||||
radius="md"
|
||||
withBorder
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => navigate({ to: `/tournaments/${tournament.id}` })}
|
||||
>
|
||||
<Stack>
|
||||
<Flex align='center' gap='md'>
|
||||
<Flex align="center" gap="md">
|
||||
<Image
|
||||
src={tournament.logo ? `/api/files/tournaments/${tournament.id}/${tournament.logo}` : undefined}
|
||||
maw={100}
|
||||
mah={100}
|
||||
fit='contain'
|
||||
fit="contain"
|
||||
src={
|
||||
tournament.logo
|
||||
? `/api/files/tournaments/${tournament.id}/${tournament.logo}`
|
||||
: undefined
|
||||
}
|
||||
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>
|
||||
{displayDate && <Text c='dimmed' size='xs' fw={600}>{displayDate}</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 ta="center" mx="auto" gap="0">
|
||||
<Text size="lg" fw={800}>
|
||||
{tournament.name} <CaretRightIcon size={12} weight="bold" />
|
||||
</Text>
|
||||
{displayDate && (
|
||||
<Text c="dimmed" size="xs" fw={600}>
|
||||
{displayDate}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,106 +2,124 @@ 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";
|
||||
import { TournamentFormInput } from "@/features/tournaments/types";
|
||||
import { TournamentInput } from "@/features/tournaments/types";
|
||||
import { isNotEmpty } from "@mantine/form";
|
||||
import useCreateTournament from "../hooks/use-create-tournament";
|
||||
import useUpdateTournament from "../hooks/use-update-tournament";
|
||||
import toast from '@/lib/sonner';
|
||||
import toast from "@/lib/sonner";
|
||||
import { logger } from "..";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { tournamentQueries } from "@/features/tournaments/queries";
|
||||
import { tournamentKeys } from "@/features/tournaments/queries";
|
||||
import { DateTimePicker } from "@mantine/dates";
|
||||
import { useCallback } from "react";
|
||||
|
||||
interface TournamentFormProps {
|
||||
close: () => void;
|
||||
initialValues?: Partial<TournamentFormInput>;
|
||||
initialValues?: Partial<TournamentInput>;
|
||||
tournamentId?: string;
|
||||
}
|
||||
|
||||
const TournamentForm = ({ close, initialValues, tournamentId }: TournamentFormProps) => {
|
||||
const TournamentForm = ({
|
||||
close,
|
||||
initialValues,
|
||||
tournamentId,
|
||||
}: TournamentFormProps) => {
|
||||
const isEditMode = !!tournamentId;
|
||||
|
||||
const config: UseFormInput<TournamentFormInput> = {
|
||||
const config: UseFormInput<TournamentInput> = {
|
||||
initialValues: {
|
||||
name: initialValues?.name || '',
|
||||
location: initialValues?.location || '',
|
||||
desc: initialValues?.desc || '',
|
||||
start_time: initialValues?.start_time || '',
|
||||
enroll_time: initialValues?.enroll_time || '',
|
||||
end_time: initialValues?.end_time || '',
|
||||
name: initialValues?.name || "",
|
||||
location: initialValues?.location || "",
|
||||
desc: initialValues?.desc || "",
|
||||
start_time: initialValues?.start_time || "",
|
||||
enroll_time: initialValues?.enroll_time || "",
|
||||
end_time: initialValues?.end_time || "",
|
||||
logo: undefined,
|
||||
},
|
||||
onSubmitPreventDefault: 'always',
|
||||
onSubmitPreventDefault: "always",
|
||||
validate: {
|
||||
name: isNotEmpty('Name is required'),
|
||||
location: isNotEmpty('Location is required'),
|
||||
start_time: isNotEmpty('Start time is required'),
|
||||
enroll_time: isNotEmpty('Enrollment time is required'),
|
||||
}
|
||||
}
|
||||
name: isNotEmpty("Name is required"),
|
||||
location: isNotEmpty("Location is required"),
|
||||
start_time: isNotEmpty("Start time is required"),
|
||||
enroll_time: isNotEmpty("Enrollment time is required"),
|
||||
},
|
||||
};
|
||||
|
||||
const form = useForm(config);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: createTournament, isPending: createPending } = useCreateTournament();
|
||||
const { mutate: updateTournament, isPending: updatePending } = useUpdateTournament(tournamentId || '');
|
||||
|
||||
const { mutate: createTournament, isPending: createPending } =
|
||||
useCreateTournament();
|
||||
const { mutate: updateTournament, isPending: updatePending } =
|
||||
useUpdateTournament(tournamentId || "");
|
||||
|
||||
const isPending = createPending || updatePending;
|
||||
|
||||
const handleSubmit = useCallback(async (values: TournamentFormInput) => {
|
||||
const { logo, ...tournamentData } = values;
|
||||
|
||||
const mutation = isEditMode ? updateTournament : createTournament;
|
||||
const successMessage = isEditMode ? 'Tournament updated successfully!' : 'Tournament created successfully!';
|
||||
const errorMessage = isEditMode ? 'Failed to update tournament' : 'Failed to create tournament';
|
||||
|
||||
mutation(tournamentData, {
|
||||
onSuccess: async (tournament) => {
|
||||
if (logo && tournament) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('tournamentId', tournament.id);
|
||||
formData.append('logo', logo);
|
||||
const handleSubmit = useCallback(
|
||||
async (values: TournamentInput) => {
|
||||
const { logo, ...tournamentData } = values;
|
||||
|
||||
const response = await fetch('/api/tournaments/upload-logo', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
const mutation = isEditMode ? updateTournament : createTournament;
|
||||
const successMessage = isEditMode
|
||||
? "Tournament updated successfully!"
|
||||
: "Tournament created successfully!";
|
||||
const errorMessage = isEditMode
|
||||
? "Failed to update tournament"
|
||||
: "Failed to create tournament";
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to upload logo');
|
||||
mutation(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: tournamentKeys.list });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: tournamentKeys.details(result.tournament!.id),
|
||||
});
|
||||
queryClient.setQueryData(
|
||||
tournamentKeys.details(result.tournament!.id),
|
||||
result.tournament
|
||||
);
|
||||
|
||||
toast.success(successMessage);
|
||||
} catch (error: any) {
|
||||
const logoErrorMessage = isEditMode
|
||||
? `Tournament updated but logo upload failed: ${error.message}`
|
||||
: `Tournament created but logo upload failed: ${error.message}`;
|
||||
toast.error(logoErrorMessage);
|
||||
logger.error("Tournament logo upload error", error);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: tournamentQueries.list().queryKey });
|
||||
queryClient.invalidateQueries({ queryKey: tournamentQueries.details(result.tournament!.id).queryKey });
|
||||
queryClient.setQueryData(
|
||||
tournamentQueries.details(result.tournament!.id).queryKey,
|
||||
result.tournament
|
||||
);
|
||||
|
||||
} else {
|
||||
toast.success(successMessage);
|
||||
} catch (error: any) {
|
||||
const logoErrorMessage = isEditMode
|
||||
? `Tournament updated but logo upload failed: ${error.message}`
|
||||
: `Tournament created but logo upload failed: ${error.message}`;
|
||||
toast.error(logoErrorMessage);
|
||||
logger.error('Tournament logo upload error', error);
|
||||
}
|
||||
} else {
|
||||
toast.success(successMessage);
|
||||
}
|
||||
close();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(`${errorMessage}: ${error.message}`);
|
||||
logger.error(`Tournament ${isEditMode ? 'update' : 'create'} error`, error);
|
||||
}
|
||||
});
|
||||
}, [isEditMode, createTournament, updateTournament, queryClient]);
|
||||
close();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(`${errorMessage}: ${error.message}`);
|
||||
logger.error(
|
||||
`Tournament ${isEditMode ? "update" : "create"} error`,
|
||||
error
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
[isEditMode, createTournament, updateTournament, queryClient]
|
||||
);
|
||||
|
||||
return (
|
||||
<SlidePanel
|
||||
@@ -115,83 +133,91 @@ const TournamentForm = ({ close, initialValues, tournamentId }: TournamentFormPr
|
||||
<TextInput
|
||||
label="Name"
|
||||
withAsterisk
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
key={form.key("name")}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<TextInput
|
||||
label="Location"
|
||||
withAsterisk
|
||||
key={form.key('location')}
|
||||
{...form.getInputProps('location')}
|
||||
key={form.key("location")}
|
||||
{...form.getInputProps("location")}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Description"
|
||||
key={form.key('desc')}
|
||||
{...form.getInputProps('desc')}
|
||||
key={form.key("desc")}
|
||||
{...form.getInputProps("desc")}
|
||||
minRows={3}
|
||||
/>
|
||||
|
||||
<FileInput
|
||||
key={form.key('logo')}
|
||||
key={form.key("logo")}
|
||||
accept="image/png,image/jpeg,image/gif,image/jpg"
|
||||
label={isEditMode ? "Change Logo" : "Logo"}
|
||||
leftSection={<LinkIcon size={16} />}
|
||||
{...form.getInputProps('logo')}
|
||||
{...form.getInputProps("logo")}
|
||||
/>
|
||||
|
||||
<SlidePanelField
|
||||
key={form.key('start_time')}
|
||||
{...form.getInputProps('start_time')}
|
||||
key={form.key("start_time")}
|
||||
{...form.getInputProps("start_time")}
|
||||
Component={DateTimePicker}
|
||||
title="Select Start Date"
|
||||
label="Start Date"
|
||||
withAsterisk
|
||||
formatValue={(date) => new Date(date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
})}
|
||||
formatValue={(date) =>
|
||||
new Date(date).toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<SlidePanelField
|
||||
key={form.key('enroll_time')}
|
||||
{...form.getInputProps('enroll_time')}
|
||||
key={form.key("enroll_time")}
|
||||
{...form.getInputProps("enroll_time")}
|
||||
Component={DateTimePicker}
|
||||
title="Select Enrollment Due Date"
|
||||
label="Enrollment Due"
|
||||
withAsterisk
|
||||
formatValue={(date) => new Date(date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
})}
|
||||
formatValue={(date) =>
|
||||
new Date(date).toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{isEditMode && (
|
||||
<SlidePanelField
|
||||
key={form.key('end_time')}
|
||||
{...form.getInputProps('end_time')}
|
||||
key={form.key("end_time")}
|
||||
{...form.getInputProps("end_time")}
|
||||
Component={DateTimePicker}
|
||||
title="Select End Date"
|
||||
label="End Date (Optional)"
|
||||
formatValue={(date) => date ? new Date(date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
}) : 'Not set'}
|
||||
formatValue={(date) =>
|
||||
date
|
||||
? new Date(date).toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
})
|
||||
: "Not set"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -199,4 +225,4 @@ const TournamentForm = ({ close, initialValues, tournamentId }: TournamentFormPr
|
||||
);
|
||||
};
|
||||
|
||||
export default TournamentForm;
|
||||
export default TournamentForm;
|
||||
|
||||
Reference in New Issue
Block a user