free agents
This commit is contained in:
@@ -107,7 +107,7 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
</Text>
|
||||
<Stack gap={1}>
|
||||
{match.home?.players.map((p) => (
|
||||
<Text size="xs" fw={600} c="dimmed" ta="right">
|
||||
<Text key={`match-card-p-${p.id}`} size="xs" fw={600} c="dimmed" ta="right">
|
||||
{p.first_name} {p.last_name}
|
||||
</Text>
|
||||
))}
|
||||
@@ -163,7 +163,7 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
</Text>
|
||||
<Stack gap={1}>
|
||||
{match.away?.players.map((p) => (
|
||||
<Text size="xs" fw={600} c="dimmed" ta="right">
|
||||
<Text key={`match-card-p-${p.id}`} size="xs" fw={600} c="dimmed" ta="right">
|
||||
{p.first_name} {p.last_name}
|
||||
</Text>
|
||||
))}
|
||||
|
||||
@@ -21,11 +21,18 @@ export const fetchMe = createServerFn()
|
||||
return {
|
||||
user: result || undefined,
|
||||
roles: context.roles,
|
||||
metadata: context.metadata
|
||||
metadata: context.metadata,
|
||||
phone: context.phone
|
||||
};
|
||||
} catch (error: any) {
|
||||
logger.info('fetchMe: Session error', error.message);
|
||||
return { user: undefined, roles: [], metadata: {} };
|
||||
logger.info("FetchMe: Session error", error)
|
||||
if (error?.response?.status === 401) {
|
||||
const errorData = error?.response?.data;
|
||||
if (errorData?.error === "SESSION_REFRESH_REQUIRED") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return { user: undefined, roles: [], metadata: {}, phone: undefined };
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -146,4 +153,4 @@ export const getUnenrolledPlayers = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) =>
|
||||
toServerResult(async () => await pbAdmin.getUnenrolledPlayers(tournamentId))
|
||||
);
|
||||
);
|
||||
|
||||
@@ -58,15 +58,12 @@ const EmojiBar = ({
|
||||
return reaction.players.map(p => p.id).includes(user?.id || "");
|
||||
}, [user?.id]);
|
||||
|
||||
// Get emojis the current user has reacted to
|
||||
const userReactions = reactions?.filter(r => hasReacted(r)).map(r => r.emoji) || [];
|
||||
|
||||
if (!reactions) return;
|
||||
|
||||
// Sort reactions by count (descending)
|
||||
const sortedReactions = [...reactions].sort((a, b) => b.count - a.count);
|
||||
|
||||
// Group reactions: show first 3, group the rest
|
||||
const visibleReactions = sortedReactions.slice(0, 3);
|
||||
const groupedReactions = sortedReactions.slice(3);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useServerQuery, useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { useServerMutation, useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
||||
import { getMatchReactions, toggleMatchReaction } from "@/features/matches/server";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
@@ -14,7 +14,7 @@ export const reactionQueries = {
|
||||
};
|
||||
|
||||
export const useMatchReactions = (matchId: string) =>
|
||||
useServerQuery(reactionQueries.match(matchId));
|
||||
useServerSuspenseQuery(reactionQueries.match(matchId));
|
||||
|
||||
export const useToggleMatchReaction = (matchId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -27,4 +27,4 @@ export const useToggleMatchReaction = (matchId: string) => {
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,6 +69,7 @@ const TeamList = ({ teams, loading = false }: TeamListProps) => {
|
||||
{teams?.map((team) => (
|
||||
<div key={team.id}>
|
||||
<ListItem
|
||||
key={`team-list-${team.id}`}
|
||||
p="xs"
|
||||
icon={
|
||||
<Avatar
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Box, Card, Center, Divider, Group, Skeleton, Stack } from "@mantine/core";
|
||||
|
||||
const StartedTournamentSkeleton = () => {
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Header skeleton */}
|
||||
<Stack px="md">
|
||||
<Group justify="space-between" align="flex-start">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Skeleton height={32} width="60%" mb="xs" />
|
||||
<Skeleton height={16} width="40%" />
|
||||
</Box>
|
||||
<Skeleton height={60} width={60} radius="md" />
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
{/* Match carousel skeleton */}
|
||||
<Box>
|
||||
<Group gap="xs" px="xl">
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
withBorder
|
||||
radius="lg"
|
||||
p="lg"
|
||||
style={{ minWidth: "95%", flex: "0 0 auto" }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{/* Match header */}
|
||||
<Group justify="space-between">
|
||||
<Skeleton height={14} width="30%" />
|
||||
<Skeleton height={20} width={60} radius="xl" />
|
||||
</Group>
|
||||
|
||||
{/* Teams */}
|
||||
<Stack gap="sm">
|
||||
<Group>
|
||||
<Skeleton height={32} width={32} radius="sm" />
|
||||
<Skeleton height={16} width="40%" />
|
||||
<Box ml="auto">
|
||||
<Skeleton height={24} width={30} />
|
||||
</Box>
|
||||
</Group>
|
||||
<Center>
|
||||
<Skeleton height={14} width={20} />
|
||||
</Center>
|
||||
<Group>
|
||||
<Skeleton height={32} width={32} radius="sm" />
|
||||
<Skeleton height={16} width="40%" />
|
||||
<Box ml="auto">
|
||||
<Skeleton height={24} width={30} />
|
||||
</Box>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
))}
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{/* Actions section skeleton */}
|
||||
<Box>
|
||||
<Divider />
|
||||
<Stack gap={0}>
|
||||
<Skeleton height={48} width="100%" />
|
||||
<Skeleton height={48} width="100%" />
|
||||
<Skeleton height={48} width="100%" />
|
||||
<Skeleton height={48} width="100%" />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default StartedTournamentSkeleton;
|
||||
@@ -2,11 +2,23 @@ import Button from "@/components/button";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { Text } from "@mantine/core";
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import useEnrollFreeAgent from "../../hooks/use-enroll-free-agent";
|
||||
|
||||
const EnrollFreeAgent = () => {
|
||||
const EnrollFreeAgent = ({ tournamentId }: {tournamentId: string} ) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
const { user } = useAuth();
|
||||
const { user, phone } = useAuth();
|
||||
|
||||
const { mutate: enrollFreeAgent, isPending: isEnrolling } = useEnrollFreeAgent();
|
||||
const handleEnroll = () => {
|
||||
console.log('enrolling...')
|
||||
enrollFreeAgent({ playerId: user!.id, tournamentId, phone }, {
|
||||
onSuccess: () => {
|
||||
toggle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="subtle" size="sm" onClick={open}>
|
||||
@@ -14,13 +26,19 @@ const EnrollFreeAgent = () => {
|
||||
</Button>
|
||||
|
||||
<Sheet title="Free Agent Enrollment" opened={isOpen} onChange={toggle}>
|
||||
<Text size="md" mb="md">
|
||||
Enrolling as a free agent will enter you in a pool of players wanting to play but don't have a teammate yet.
|
||||
</Text>
|
||||
<Text size="sm" mb="md" c='dimmed'>
|
||||
You will be automatically paired with a partner before the tournament starts, and you will be able to see your new team and set a walkout song in the app.
|
||||
</Text>
|
||||
<Button onClick={console.log}>Confirm</Button>
|
||||
<Stack gap="xs">
|
||||
<Text size="md">
|
||||
Enrolling as a free agent will enter you in a pool of players wanting to play but don't have a teammate yet.
|
||||
</Text>
|
||||
<Text size="sm" c='dimmed'>
|
||||
You will be able to see a list of other enrolled free agents, as well as their contact information for organizing your team and walkout song. By enrolling, your phone number will be visible to other free agents.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Note: this does not guarantee you a spot in the tournament. One person from your team must enroll in the app and choose a walkout song in order to secure a spot.
|
||||
</Text>
|
||||
<Button onClick={handleEnroll}>Confirm</Button>
|
||||
<Button variant="subtle" color="red" onClick={toggle}>Cancel</Button>
|
||||
</Stack>
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Group, Stack, Text, Card, Badge, Box, ActionIcon } from "@mantine/core";
|
||||
import { UserIcon, PhoneIcon } from "@phosphor-icons/react";
|
||||
import { useFreeAgents } from "../../queries";
|
||||
import UnenrollFreeAgent from "./unenroll-free-agent";
|
||||
import toast from "@/lib/sonner";
|
||||
|
||||
const EnrolledFreeAgent: React.FC<{ tournamentId: string }> = ({
|
||||
tournamentId
|
||||
}) => {
|
||||
const { data: freeAgents } = useFreeAgents(tournamentId);
|
||||
|
||||
const copyToClipboard = async (phone: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(phone);
|
||||
toast.success("Phone number copied!");
|
||||
} catch (err) {
|
||||
toast.success("Failed to copy");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" align="center">
|
||||
<Group gap="xs" align="center">
|
||||
<UserIcon size={16} />
|
||||
<Text size="sm" fw={500}>
|
||||
Enrolled as Free Agent
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Text size="xs" c="dimmed">
|
||||
You're on the free agent list. Other free agents looking for teams:
|
||||
</Text>
|
||||
|
||||
{freeAgents.length > 1 ? (
|
||||
<Card withBorder radius="md" p="sm">
|
||||
<Stack gap="xs">
|
||||
{freeAgents
|
||||
.filter(agent => agent.player)
|
||||
.map((agent) => (
|
||||
<Group key={agent.id} justify="space-between" align="center" wrap="nowrap">
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{agent.player?.first_name} {agent.player?.last_name}
|
||||
</Text>
|
||||
</Box>
|
||||
{agent.phone && (
|
||||
<Group gap={4} align="center" style={{ flexShrink: 0 }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={() => copyToClipboard(agent.phone!)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<PhoneIcon size={12} />
|
||||
</ActionIcon>
|
||||
<Text
|
||||
size="xs"
|
||||
c="dimmed"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => copyToClipboard(agent.phone!)}
|
||||
>
|
||||
{agent.phone}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
))}
|
||||
|
||||
{freeAgents.length > 1 && (
|
||||
<Badge
|
||||
variant="light"
|
||||
size="xs"
|
||||
color="blue"
|
||||
style={{ alignSelf: 'flex-start', marginTop: '4px' }}
|
||||
>
|
||||
{freeAgents.length} free agents total
|
||||
</Badge>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
) : (
|
||||
<Card withBorder radius="md" p="sm">
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
You're the only free agent so far
|
||||
</Text>
|
||||
</Card>
|
||||
)}
|
||||
<UnenrollFreeAgent tournamentId={tournamentId} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrolledFreeAgent;
|
||||
@@ -14,10 +14,6 @@ const Header = ({ tournament }: { tournament: Tournament }) => {
|
||||
() => new Date(tournament.start_time),
|
||||
[tournament.start_time]
|
||||
);
|
||||
const teamCount = useMemo(
|
||||
() => tournament.teams?.length || 0,
|
||||
[tournament.teams]
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack align="center" gap={0}>
|
||||
|
||||
@@ -15,8 +15,9 @@ import TeamCard from "@/features/teams/components/team-card";
|
||||
import UpdateTeam from "./update-team";
|
||||
import UnenrollTeam from "./unenroll-team";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { tournamentKeys } from "../../queries";
|
||||
import { tournamentKeys, useFreeAgents } from "../../queries";
|
||||
import RulesListButton from "./rules-list-button";
|
||||
import EnrolledFreeAgent from "./enrolled-free-agent";
|
||||
|
||||
const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
tournament,
|
||||
@@ -40,57 +41,79 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const handleSubmit = () => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.current })
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.current });
|
||||
};
|
||||
|
||||
const { data: free_agents } = useFreeAgents(tournament.id);
|
||||
const isFreeAgent = useMemo(() => !isUserEnrolled && free_agents.find(a => a.player?.id === user?.id), [free_agents, isUserEnrolled]);
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Header tournament={tournament} />
|
||||
{tournament.desc && <Text size="sm">{tournament.desc}</Text>}
|
||||
|
||||
<Card withBorder radius="lg" p="lg">
|
||||
<Stack gap="xs">
|
||||
<Group mb="sm" gap="xs" align="center">
|
||||
<UsersIcon size={16} />
|
||||
<Text size="sm" fw={500}>
|
||||
Enrollment
|
||||
</Text>
|
||||
{isEnrollmentOpen && (
|
||||
<Box ml="auto">
|
||||
<Countdown
|
||||
date={enrollmentDeadline}
|
||||
label="Time left"
|
||||
color="yellow"
|
||||
/>
|
||||
</Box>
|
||||
<Stack px="md">
|
||||
{tournament.desc && <Text size="sm">{tournament.desc}</Text>}
|
||||
|
||||
<Card withBorder radius="lg" p="lg">
|
||||
<Stack gap="xs">
|
||||
<Group mb="sm" gap="xs" align="center">
|
||||
<UsersIcon size={16} />
|
||||
<Text size="sm" fw={500}>
|
||||
Enrollment
|
||||
</Text>
|
||||
{isEnrollmentOpen && (
|
||||
<Box ml="auto">
|
||||
<Countdown
|
||||
date={enrollmentDeadline}
|
||||
label="Time left"
|
||||
color="yellow"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{!isUserEnrolled && !isEnrollmentOpen && (
|
||||
<Text fw={600} c="dimmed" size="sm">
|
||||
Enrollment has been closed for this tournament.
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{!isUserEnrolled &&!isEnrollmentOpen && (
|
||||
<Text fw={600} c="dimmed" size="sm">
|
||||
Enrollment has been closed for this tournament.
|
||||
</Text>
|
||||
)}
|
||||
{!isUserEnrolled && isEnrollmentOpen && !isFreeAgent && (
|
||||
<>
|
||||
<EnrollTeam
|
||||
tournamentId={tournament.id}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
<Divider my={0} label="or" />
|
||||
<EnrollFreeAgent tournamentId={tournament.id} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isUserEnrolled &&isEnrollmentOpen && (
|
||||
<>
|
||||
<EnrollTeam tournamentId={tournament.id} onSubmit={handleSubmit} />
|
||||
<Divider my={0} label="or" />
|
||||
<EnrollFreeAgent />
|
||||
</>
|
||||
)}
|
||||
{isUserEnrolled && (
|
||||
<>
|
||||
<Suspense fallback={<TeamCardSkeleton />}>
|
||||
<TeamCard teamId={userTeam.id} />
|
||||
</Suspense>
|
||||
<UpdateTeam tournamentId={tournament.id} teamId={userTeam.id} />
|
||||
{isEnrollmentOpen && (
|
||||
<UnenrollTeam
|
||||
tournamentId={tournament.id}
|
||||
teamId={userTeam.id}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{
|
||||
isUserEnrolled && <>
|
||||
<Suspense fallback={<TeamCardSkeleton />}>
|
||||
<TeamCard teamId={userTeam.id} />
|
||||
</Suspense>
|
||||
<UpdateTeam tournamentId={tournament.id} teamId={userTeam.id} />
|
||||
{ isEnrollmentOpen && <UnenrollTeam tournamentId={tournament.id} teamId={userTeam.id} onSubmit={handleSubmit} />}
|
||||
</>
|
||||
}
|
||||
</Stack>
|
||||
</Card>
|
||||
{
|
||||
isFreeAgent && isEnrollmentOpen && (
|
||||
<EnrolledFreeAgent tournamentId={tournament.id} />
|
||||
)
|
||||
}
|
||||
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
<Box>
|
||||
<Divider />
|
||||
@@ -102,10 +125,10 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
/>
|
||||
)}
|
||||
<ListLink
|
||||
label={`View Bracket`}
|
||||
to={`/tournaments/${tournament.id}/bracket`}
|
||||
Icon={TreeStructureIcon}
|
||||
/>
|
||||
label={`View Bracket`}
|
||||
to={`/tournaments/${tournament.id}/bracket`}
|
||||
Icon={TreeStructureIcon}
|
||||
/>
|
||||
<RulesListButton tournamentId={tournament.id} />
|
||||
<TeamListButton teams={tournament.teams || []} />
|
||||
</Box>
|
||||
|
||||
@@ -20,6 +20,7 @@ const RulesListButton: React.FC<RulesListButtonProps> = ({ tournamentId }) => {
|
||||
extensions: [StarterKit],
|
||||
content: tournament?.rules || '',
|
||||
editable: false,
|
||||
immediatelyRender: false,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Box, Card, Divider, Flex, Group, Skeleton, Stack } from "@mantine/core";
|
||||
|
||||
const UpcomingTournamentSkeleton = () => {
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Flex px="md" justify="center" w="100%">
|
||||
<Skeleton height={240} width={240} radius="md" />
|
||||
<Stack justify="space-between" align="flex-start">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Skeleton height={32} mb="xs" />
|
||||
<Skeleton height={16} />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Flex>
|
||||
|
||||
<Stack px="md">
|
||||
<Skeleton height={14} width="80%" />
|
||||
|
||||
<Card withBorder radius="lg" p="lg">
|
||||
<Group mb="sm" gap="xs" align="center">
|
||||
<Skeleton height={16} width={16} />
|
||||
<Skeleton height={14} width="20%" />
|
||||
<Box ml="auto">
|
||||
<Skeleton height={20} width={80} radius="sm" />
|
||||
</Box>
|
||||
</Group>
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
<Box>
|
||||
<Divider />
|
||||
<Stack gap={0}>
|
||||
<Skeleton height={48} width="100%" />
|
||||
<Skeleton height={48} width="100%" />
|
||||
<Skeleton height={48} width="100%" />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpcomingTournamentSkeleton;
|
||||
@@ -0,0 +1,40 @@
|
||||
import Button from "@/components/button";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import useUnenrollFreeAgent from "../../hooks/use-unenroll-free-agent";
|
||||
|
||||
const UnenrollFreeAgent = ({ tournamentId }: {tournamentId: string} ) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
const { user } = useAuth();
|
||||
|
||||
const { mutate: unenrollFreeAgent, isPending: isEnrolling } = useUnenrollFreeAgent();
|
||||
const handleUnenroll = () => {
|
||||
unenrollFreeAgent({ playerId: user!.id, tournamentId }, {
|
||||
onSuccess: () => {
|
||||
toggle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="subtle" size="sm" onClick={open}>
|
||||
Unenroll
|
||||
</Button>
|
||||
|
||||
<Sheet title="Are you sure?" opened={isOpen} onChange={toggle}>
|
||||
<Stack gap="xs">
|
||||
<Text size="md">
|
||||
This will remove you from the free agent list.
|
||||
</Text>
|
||||
<Button onClick={handleUnenroll}>Confirm</Button>
|
||||
<Button variant="subtle" color="red" onClick={toggle}>Cancel</Button>
|
||||
</Stack>
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnenrollFreeAgent;
|
||||
20
src/features/tournaments/hooks/use-enroll-free-agent.ts
Normal file
20
src/features/tournaments/hooks/use-enroll-free-agent.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { enrollFreeAgent } from "@/features/tournaments/server";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
const useEnrollFreeAgent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: (data: { tournamentId: string, playerId: string, phone: string }) => {
|
||||
return enrollFreeAgent({ data });
|
||||
},
|
||||
onSuccess: (data, { tournamentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.free_agents(tournamentId) });
|
||||
},
|
||||
successMessage: 'You\'ve been added as a free agent!',
|
||||
});
|
||||
};
|
||||
|
||||
export default useEnrollFreeAgent;
|
||||
20
src/features/tournaments/hooks/use-unenroll-free-agent.ts
Normal file
20
src/features/tournaments/hooks/use-unenroll-free-agent.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { unenrollFreeAgent } from "@/features/tournaments/server";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
const useUnenrollFreeAgent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: (data: { tournamentId: string, playerId: string }) => {
|
||||
return unenrollFreeAgent({ data });
|
||||
},
|
||||
onSuccess: (data, { tournamentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.free_agents(tournamentId) });
|
||||
},
|
||||
successMessage: 'You\'ve been removed as a free agent.',
|
||||
});
|
||||
};
|
||||
|
||||
export default useUnenrollFreeAgent;
|
||||
@@ -1,11 +1,12 @@
|
||||
import { getCurrentTournament, getTournament, getUnenrolledTeams, listTournaments } from "./server";
|
||||
import { getCurrentTournament, getFreeAgents, getTournament, getUnenrolledTeams, listTournaments } from "./server";
|
||||
import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
||||
|
||||
export const tournamentKeys = {
|
||||
list: ['tournaments', 'list'] as const,
|
||||
details: (id: string) => ['tournaments', 'details', id] as const,
|
||||
current: ['tournaments', 'current'] as const,
|
||||
unenrolled: (id: string) => ['tournaments', 'unenrolled', id] as const
|
||||
unenrolled: (id: string) => ['tournaments', 'unenrolled', id] as const,
|
||||
free_agents: (id: string) => ['tournaments', 'free_agents', id] as const
|
||||
};
|
||||
|
||||
export const tournamentQueries = {
|
||||
@@ -24,6 +25,10 @@ export const tournamentQueries = {
|
||||
unenrolled: (id: string) => ({
|
||||
queryKey: tournamentKeys.unenrolled(id),
|
||||
queryFn: () => getUnenrolledTeams({ data: id })
|
||||
}),
|
||||
free_agents: (id: string) => ({
|
||||
queryKey: tournamentKeys.free_agents(id),
|
||||
queryFn: () => getFreeAgents({ data: id })
|
||||
})
|
||||
};
|
||||
|
||||
@@ -38,3 +43,6 @@ export const useCurrentTournament = () =>
|
||||
|
||||
export const useUnenrolledTeams = (tournamentId: string) =>
|
||||
useServerSuspenseQuery(tournamentQueries.unenrolled(tournamentId));
|
||||
|
||||
export const useFreeAgents = (tournamentId: string) =>
|
||||
useServerSuspenseQuery(tournamentQueries.free_agents(tournamentId));
|
||||
|
||||
@@ -83,4 +83,39 @@ export const getUnenrolledTeams = createServerFn()
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) =>
|
||||
toServerResult(() => pbAdmin.getUnenrolledTeams(tournamentId))
|
||||
);
|
||||
);
|
||||
|
||||
export const getFreeAgents = createServerFn()
|
||||
.validator(z.string())
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) =>
|
||||
toServerResult(() => pbAdmin.getFreeAgents(tournamentId))
|
||||
);
|
||||
|
||||
export const enrollFreeAgent = createServerFn()
|
||||
.validator(z.object({ phone: z.string(), tournamentId: z.string() }))
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
const player = await pbAdmin.getPlayerByAuthId(userAuthId);
|
||||
if (!player) throw new Error("Player not found");
|
||||
|
||||
await pbAdmin.enrollFreeAgent(player.id, data.phone, data.tournamentId);
|
||||
logger.info('Player enrolled as free agent', { playerId: player.id, phone: data.phone });
|
||||
})
|
||||
);
|
||||
|
||||
export const unenrollFreeAgent = createServerFn()
|
||||
.validator(z.object({ tournamentId: z.string() }))
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
const player = await pbAdmin.getPlayerByAuthId(userAuthId);
|
||||
if (!player) throw new Error("Player not found");
|
||||
|
||||
await pbAdmin.unenrollFreeAgent(player.id, data.tournamentId);
|
||||
logger.info('Player unenrolled as free agent', { playerId: player.id });
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user