From 324e1742f18d6f05a4381315748333888ad2f8d7 Mon Sep 17 00:00:00 2001 From: yohlo Date: Sun, 24 Aug 2025 00:35:38 -0500 Subject: [PATCH] settings link, uneroll team server fn --- src/app/routes/_authed/profile.$playerId.tsx | 5 ++-- .../_authed/tournaments/$tournamentId.tsx | 5 ++-- src/features/core/components/header.tsx | 4 ++- .../core/components/settings-button.tsx | 26 ++++++++++++++++ src/features/core/types/header-config.ts | 1 + .../tournaments/hooks/use-unenroll-team.ts | 30 +++++++++++++++++++ src/features/tournaments/server.ts | 17 +++++++++++ src/lib/pocketbase/services/tournaments.ts | 11 +++++++ src/lib/pocketbase/util/transform-types.ts | 27 +++++++++++------ 9 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 src/features/core/components/settings-button.tsx create mode 100644 src/features/tournaments/hooks/use-unenroll-team.ts diff --git a/src/app/routes/_authed/profile.$playerId.tsx b/src/app/routes/_authed/profile.$playerId.tsx index 74eb2a3..51d7dcc 100644 --- a/src/app/routes/_authed/profile.$playerId.tsx +++ b/src/app/routes/_authed/profile.$playerId.tsx @@ -12,10 +12,11 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({ player } }, - loader: ({ params }) => ({ + loader: ({ params, context }) => ({ header: { collapsed: true, - withBackButton: true + withBackButton: true, + settingsLink: context?.auth.user.id === params.playerId ? 'settings' : undefined }, refresh: { toRefresh: [playerQueries.details(params.playerId).queryKey], diff --git a/src/app/routes/_authed/tournaments/$tournamentId.tsx b/src/app/routes/_authed/tournaments/$tournamentId.tsx index cbd2275..05cff52 100644 --- a/src/app/routes/_authed/tournaments/$tournamentId.tsx +++ b/src/app/routes/_authed/tournaments/$tournamentId.tsx @@ -13,10 +13,11 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({ const { queryClient } = context; await queryClient.ensureQueryData(tournamentQueries.details(params.tournamentId)) }, - loader: ({ params }) => ({ + loader: ({ params, context }) => ({ header: { collapsed: true, - withBackButton: true + withBackButton: true, + settingsLink: context.auth.roles.includes("Admin") ? `/admin/tournaments/${params.tournamentId}` : undefined }, refresh: { toRefresh: tournamentQueries.details(params.tournamentId).queryKey, diff --git a/src/features/core/components/header.tsx b/src/features/core/components/header.tsx index b75c0ef..c6b2374 100644 --- a/src/features/core/components/header.tsx +++ b/src/features/core/components/header.tsx @@ -2,17 +2,19 @@ import { Title, AppShell, Flex } from "@mantine/core"; import { HeaderConfig } from "../types/header-config"; import BackButton from "./back-button"; import { useMemo } from "react"; +import SettingsButton from "./settings-button"; interface HeaderProps extends HeaderConfig { scrollPosition: { x: number, y: number }; } -const Header = ({ withBackButton, collapsed, title, scrollPosition }: HeaderProps) => { +const Header = ({ withBackButton, settingsLink, collapsed, title, scrollPosition }: HeaderProps) => { const offsetY = useMemo(() => { return collapsed ? scrollPosition.y : 0; }, [collapsed, scrollPosition.y]); return ( <> + {settingsLink && } {withBackButton && } diff --git a/src/features/core/components/settings-button.tsx b/src/features/core/components/settings-button.tsx new file mode 100644 index 0000000..517b96d --- /dev/null +++ b/src/features/core/components/settings-button.tsx @@ -0,0 +1,26 @@ +import { Box } from "@mantine/core" +import { GearIcon } from "@phosphor-icons/react" +import { useNavigate } from "@tanstack/react-router" + +interface SettingButtonProps { + offsetY: number; + to: string; +} + +const SettingsButton = ({ offsetY, to }: SettingButtonProps) => { + const navigate = useNavigate(); + + return ( + navigate({ to })} + pos='absolute' + right={{ base: 0, sm: 100, md: 200, lg: 300 }} + m={20} + > + + + ); +} + +export default SettingsButton; diff --git a/src/features/core/types/header-config.ts b/src/features/core/types/header-config.ts index 7c8ae47..130d640 100644 --- a/src/features/core/types/header-config.ts +++ b/src/features/core/types/header-config.ts @@ -2,6 +2,7 @@ interface HeaderConfig { title?: string; withBackButton?: boolean; collapsed?: boolean; + settingsLink?: string; } export type { HeaderConfig }; diff --git a/src/features/tournaments/hooks/use-unenroll-team.ts b/src/features/tournaments/hooks/use-unenroll-team.ts new file mode 100644 index 0000000..073ea1f --- /dev/null +++ b/src/features/tournaments/hooks/use-unenroll-team.ts @@ -0,0 +1,30 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { unenrollTeam } from "@/features/tournaments/server"; +import toast from '@/lib/sonner'; + +const useUnenrollTeam = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: { tournamentId: string, teamId: string }) => { + return unenrollTeam({ data }); + }, + onSuccess: (data, { tournamentId }) => { + if (!data) { + toast.error('There was an issue unenrolling. Please try again later.'); + } else { + queryClient.invalidateQueries({ queryKey: ['tournaments', 'detail', tournamentId] }); + toast.success('Team unenrolled successfully.'); + } + }, + onError: (error: any) => { + if (error.message) { + toast.error(error.message); + } else { + toast.error('An unexpected error occurred when trying to unenroll the team. Please try again later.'); + } + }, + }); +}; + +export default useUnenrollTeam; diff --git a/src/features/tournaments/server.ts b/src/features/tournaments/server.ts index 651b101..70ed5e5 100644 --- a/src/features/tournaments/server.ts +++ b/src/features/tournaments/server.ts @@ -70,3 +70,20 @@ export const enrollTeam = createServerFn() throw error; } }); + +export const unenrollTeam = createServerFn() + .validator(z.object({ + tournamentId: z.string(), + teamId: z.string() + })) + .middleware([superTokensAdminFunctionMiddleware]) + .handler(async ({ data: { tournamentId, teamId }, context }) => { + try { + logger.info('Enrolling team in tournament', { tournamentId, teamId, context }); + const tournament = await pbAdmin.unenrollTeam(tournamentId, teamId); + return tournament; + } catch (error) { + logger.error('Error enrolling team', error); + throw error; + } + }); diff --git a/src/lib/pocketbase/services/tournaments.ts b/src/lib/pocketbase/services/tournaments.ts index d340d92..f368b23 100644 --- a/src/lib/pocketbase/services/tournaments.ts +++ b/src/lib/pocketbase/services/tournaments.ts @@ -57,5 +57,16 @@ export function createTournamentsService(pb: PocketBase) { ); return transformTournament(result); }, + async unenrollTeam( + tournamentId: string, + teamId: string + ): Promise { + const result = await pb.collection("tournaments").update( + tournamentId, + { "teams-": teamId }, + { expand: "teams, teams.players" } + ); + return transformTournament(result); + }, }; } diff --git a/src/lib/pocketbase/util/transform-types.ts b/src/lib/pocketbase/util/transform-types.ts index ce7623f..0f80660 100644 --- a/src/lib/pocketbase/util/transform-types.ts +++ b/src/lib/pocketbase/util/transform-types.ts @@ -7,9 +7,12 @@ import { Tournament } from "@/features/tournaments/types"; export function transformPlayer(record: any): Player { const sadf: string[] = []; - const teams = record.expand?.teams - ?.sort((a: Team, b: Team) => new Date(a.created) < new Date(b.created) ? -1 : 0) - ?.map(transformTeam) ?? []; + const teams = + record.expand?.teams + ?.sort((a: Team, b: Team) => + new Date(a.created) < new Date(b.created) ? -1 : 0 + ) + ?.map(transformTeam) ?? []; return { id: record.id!, @@ -23,9 +26,12 @@ export function transformPlayer(record: any): Player { } export function transformTeam(record: any): Team { - const players = record.expand?.players - ?.sort((a: Player, b: Player) => new Date(a.created!) < new Date(b.created!) ? -1 : 0) - ?.map(transformPlayer) ?? []; + const players = + record.expand?.players + ?.sort((a: Player, b: Player) => + new Date(a.created!) < new Date(b.created!) ? -1 : 0 + ) + ?.map(transformPlayer) ?? []; return { id: record.id, @@ -48,9 +54,12 @@ export function transformTeam(record: any): Team { } export function transformTournament(record: any): Tournament { - const teams = record.expand?.teams - ?.sort((a: Team, b: Team) => new Date(a.created) < new Date(b.created) ? -1 : 0) - ?.map(transformTeam) ?? []; + const teams = + record.expand?.teams + ?.sort((a: Team, b: Team) => + new Date(a.created) < new Date(b.created) ? -1 : 0 + ) + ?.map(transformTeam) ?? []; return { id: record.id,