diff --git a/src/app/routes/_authed/admin/tournaments/$id.tsx b/src/app/routes/_authed/admin/tournaments/$id.tsx index 3b360cb..be017f8 100644 --- a/src/app/routes/_authed/admin/tournaments/$id.tsx +++ b/src/app/routes/_authed/admin/tournaments/$id.tsx @@ -1,21 +1,28 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, redirect } from '@tanstack/react-router' import { tournamentQueries } from '@/features/tournaments/queries' import { useQuery } from '@tanstack/react-query' -import { useAuth } from '@/contexts/auth-context' -import EditTournament from '@/features/admin/components/edit-tournament' -import Page from '@/components/page' -import { Loader } from '@mantine/core' +import { List } from '@mantine/core' +import ListButton from '@/components/list-button' +import { HardDrivesIcon, PencilLineIcon, UsersThreeIcon } from '@phosphor-icons/react' +import { useSheet } from '@/hooks/use-sheet' +import Sheet from '@/components/sheet/sheet' +import TournamentForm from '@/features/tournaments/components/tournament-form' export const Route = createFileRoute('/_authed/admin/tournaments/$id')({ beforeLoad: async ({ context, params }) => { const { queryClient } = context; - await queryClient.ensureQueryData(tournamentQueries.details(params.id)) + const tournament = await queryClient.ensureQueryData(tournamentQueries.details(params.id)) + if (!tournament) throw redirect({ to: '/admin/tournaments' }); + return { + tournament + } }, - loader: () => ({ + loader: ({ context }) => ({ header: { withBackButton: true, - title: 'Edit Tournament', + title: `Manage ${context.tournament.name}`, }, + withPadding: false }), component: RouteComponent, }) @@ -25,9 +32,30 @@ function RouteComponent() { const { data: tournament } = useQuery(tournamentQueries.details(id)) if (!tournament) throw new Error("Tournament not found.") + const { isOpen: editTournamentOpened, open: openEditTournament, close: closeEditTournament } = useSheet(); + return ( - - - + <> + + + + + + + + + + ) } \ No newline at end of file diff --git a/src/app/routes/_authed/tournaments/index.tsx b/src/app/routes/_authed/tournaments/index.tsx index 42379d3..8b9c58e 100644 --- a/src/app/routes/_authed/tournaments/index.tsx +++ b/src/app/routes/_authed/tournaments/index.tsx @@ -7,7 +7,7 @@ import { useQuery } from '@tanstack/react-query' import { useAuth } from '@/contexts/auth-context' import { useSheet } from '@/hooks/use-sheet' import Sheet from '@/components/sheet/sheet' -import CreateTournament from '@/features/admin/components/create-tournament' +import TournamentForm from '@/features/tournaments/components/tournament-form' import { PlusIcon } from '@phosphor-icons/react' import Button from '@/components/button' @@ -38,7 +38,7 @@ function RouteComponent() { <> - + ) : null diff --git a/src/app/routes/api/events.$.ts b/src/app/routes/api/events.$.ts index 2584ed4..b67c086 100644 --- a/src/app/routes/api/events.$.ts +++ b/src/app/routes/api/events.$.ts @@ -5,7 +5,7 @@ import { superTokensRequestMiddleware } from "@/utils/supertokens"; export const ServerRoute = createServerFileRoute("/api/events/$").middleware([superTokensRequestMiddleware]).methods({ GET: ({ request, context }) => { - logger.info('ServerEvents | New connection', (context as any)?.userAuthId); + logger.info('ServerEvents | New connection', context?.userAuthId); const stream = new ReadableStream({ start(controller) { @@ -41,7 +41,7 @@ export const ServerRoute = createServerFileRoute("/api/events/$").middleware([su serverEvents.off("test", handleEvent); clearInterval(pingInterval); try { - logger.info('ServerEvents | Closing connection', (context as any)?.userAuthId); + logger.info('ServerEvents | Closing connection', context?.userAuthId); controller.close(); } catch (e) { logger.error('ServerEvents | Error closing controller', e); diff --git a/src/components/date-input.tsx b/src/components/date-input.tsx deleted file mode 100644 index 519a289..0000000 --- a/src/components/date-input.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { TextInput, Flex, Box, Button } from "@mantine/core"; -import { CalendarIcon } from "@phosphor-icons/react"; -import { useSheet } from "@/hooks/use-sheet"; -import Sheet from "@/components/sheet/sheet"; -import { DateTimePicker } from "../features/admin/components/date-time-picker"; - -interface DateInputProps { - label: string; - value: string; - onChange: (value: string) => void; - withAsterisk?: boolean; - error?: React.ReactNode; - placeholder?: string; -} - -// Date input that opens a sheet with mantine date time picker when clicked -export const DateInputSheet = ({ - label, - value, - onChange, - withAsterisk, - error, - placeholder -}: DateInputProps) => { - const sheet = useSheet(); - - const formatDisplayValue = (dateString: string) => { - if (!dateString) return ''; - const date = new Date(dateString); - if (isNaN(date.getTime())) return ''; - return date.toLocaleDateString('en-US', { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - hour12: true - }); - }; - - const handleDateChange = (newValue: string | null) => { - onChange(newValue || ''); - sheet.close(); - }; - - const dateValue = value ? new Date(value) : null; - - return ( - <> - } - style={{ cursor: 'pointer' }} - /> - - - - - - - - - - - ); -}; \ No newline at end of file diff --git a/src/features/admin/components/date-time-picker.tsx b/src/components/date-time-picker.tsx similarity index 100% rename from src/features/admin/components/date-time-picker.tsx rename to src/components/date-time-picker.tsx diff --git a/src/components/list-button.tsx b/src/components/list-button.tsx index 91278c6..aaca803 100644 --- a/src/components/list-button.tsx +++ b/src/components/list-button.tsx @@ -4,17 +4,17 @@ import { CaretRightIcon, Icon } from "@phosphor-icons/react"; interface ListButtonProps { label: string; Icon: Icon; - handleClick: () => void; + onClick: () => void; } -const ListButton = ({ label, handleClick, Icon }: ListButtonProps) => { +const ListButton = ({ label, onClick, Icon }: ListButtonProps) => { return ( <> diff --git a/src/components/list-link.tsx b/src/components/list-link.tsx index a8798d8..003d96f 100644 --- a/src/components/list-link.tsx +++ b/src/components/list-link.tsx @@ -5,7 +5,7 @@ import { Link, useNavigate } from "@tanstack/react-router"; interface ListLinkProps { label: string; to: string; - Icon: Icon; + Icon?: Icon; } const ListLink = ({ label, to, Icon }: ListLinkProps) => { @@ -19,7 +19,7 @@ const ListLink = ({ label, to, Icon }: ListLinkProps) => { component={'button'} onClick={() => navigate({ to })} label={{label}} - leftSection={} + leftSection={Icon && } rightSection={} /> diff --git a/src/contexts/auth-context.tsx b/src/contexts/auth-context.tsx index 471d0c4..393a99d 100644 --- a/src/contexts/auth-context.tsx +++ b/src/contexts/auth-context.tsx @@ -2,6 +2,7 @@ import { createContext, PropsWithChildren, useCallback, useContext, useMemo } fr import { MantineColor, MantineColorScheme } from "@mantine/core"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { fetchMe } from "@/features/players/server"; +import { Player } from "@/features/players/types"; const queryKey = ['auth']; export const authQueryConfig = { @@ -10,7 +11,7 @@ export const authQueryConfig = { } interface AuthData { - user: any; + user: Player | undefined; metadata: { accentColor: MantineColor; colorScheme: MantineColorScheme }; roles: string[]; } diff --git a/src/features/admin/components/admin-page.tsx b/src/features/admin/components/admin-page.tsx index b7d2897..a57fca4 100644 --- a/src/features/admin/components/admin-page.tsx +++ b/src/features/admin/components/admin-page.tsx @@ -1,15 +1,17 @@ import { Title, List, Divider } from "@mantine/core"; import ListLink from "@/components/list-link"; import Page from "@/components/page"; -import { TrophyIcon, UsersFourIcon, UsersThreeIcon } from "@phosphor-icons/react"; +import { DatabaseIcon, TrophyIcon, UsersFourIcon, UsersThreeIcon } from "@phosphor-icons/react"; import ListButton from "@/components/list-button"; const AdminPage = () => { + console.log(process.env.VITE_POCKETBASE_URL!) return ( Admin + window.location.replace(import.meta.env.VITE_POCKETBASE_URL! + "/_/")} /> ); diff --git a/src/features/admin/components/edit-tournament.tsx b/src/features/admin/components/edit-tournament.tsx deleted file mode 100644 index 86d2200..0000000 --- a/src/features/admin/components/edit-tournament.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { Box, FileInput, Group, Stack, TextInput, Textarea, Title } from "@mantine/core"; -import { useForm, UseFormInput } from "@mantine/form"; -import { LinkIcon } from "@phosphor-icons/react"; -import { Tournament, TournamentFormInput } from "@/features/tournaments/types"; -import { DateInputSheet } from "../../../components/date-input"; -import { isNotEmpty } from "@mantine/form"; -import toast from '@/lib/sonner'; -import { logger } from ".."; -import { useQueryClient, useMutation } from "@tanstack/react-query"; -import { tournamentQueries } from "@/features/tournaments/queries"; -import { updateTournament } from "@/features/tournaments/server"; -import { useRouter } from "@tanstack/react-router"; -import Button from "@/components/button"; - -interface EditTournamentProps { - tournament: Tournament; -} - -const EditTournament = ({ tournament }: EditTournamentProps) => { - const router = useRouter(); - const queryClient = useQueryClient(); - - const config: UseFormInput = { - initialValues: { - name: tournament.name || '', - location: tournament.location || '', - desc: tournament.desc || '', - start_time: tournament.start_time || '', - enroll_time: tournament.enroll_time || '', - end_time: tournament.end_time || '', - rules: tournament.rules || '', - logo: undefined, // Always start with no file selected - }, - 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'), - } - }; - - const form = useForm(config); - - const { mutate: editTournament, isPending } = useMutation({ - mutationFn: (data: Partial) => - updateTournament({ data: { id: tournament.id, updates: data } }), - onSuccess: (updatedTournament) => { - if (updatedTournament) { - queryClient.invalidateQueries({ queryKey: tournamentQueries.list().queryKey }); - queryClient.setQueryData( - tournamentQueries.details(updatedTournament.id).queryKey, - updatedTournament - ); - } - } - }); - - const handleSubmit = async (values: TournamentFormInput) => { - const { logo, ...tournamentData } = values; - - editTournament(tournamentData, { - onSuccess: async (updatedTournament) => { - if (logo && updatedTournament) { - try { - const formData = new FormData(); - formData.append('tournamentId', updatedTournament.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.setQueryData( - tournamentQueries.details(result.tournament!.id).queryKey, - result.tournament - ); - - toast.success('Tournament updated successfully!'); - } catch (error: any) { - toast.error(`Tournament updated but logo upload failed: ${error.message}`); - logger.error('Tournament logo upload error', error); - } - } else { - toast.success('Tournament updated successfully!'); - } - - router.history.back(); - }, - onError: (error: any) => { - toast.error(`Failed to update tournament: ${error.message}`); - logger.error('Tournament update error', error); - } - }); - }; - - return ( - - - Edit Tournament - -
- - - - - -