enroll team polish?
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"@tanstack/react-router-devtools": "^1.130.13",
|
||||
"@tanstack/react-router-with-query": "^1.130.12",
|
||||
"@tanstack/react-start": "^1.130.15",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tiptap/pm": "^3.4.3",
|
||||
"@tiptap/react": "^3.4.3",
|
||||
"@tiptap/starter-kit": "^3.4.3",
|
||||
|
||||
28
pb_migrations/1758123221_updated_reactions.js
Normal file
28
pb_migrations/1758123221_updated_reactions.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1549310251")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(3, new Field({
|
||||
"cascadeDelete": false,
|
||||
"collectionId": "pbc_2541054544",
|
||||
"hidden": false,
|
||||
"id": "relation2052834565",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "match",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1549310251")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("relation2052834565")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
@@ -32,7 +32,7 @@ export function createRouter() {
|
||||
defaultPreload: "intent",
|
||||
defaultErrorComponent: DefaultCatchBoundary,
|
||||
scrollRestoration: true,
|
||||
defaultViewTransition: true,
|
||||
defaultViewTransition: false,
|
||||
}),
|
||||
queryClient
|
||||
);
|
||||
|
||||
@@ -26,8 +26,8 @@ export const Route = createFileRoute("/_authed")({
|
||||
},
|
||||
pendingComponent: () => (
|
||||
<Layout>
|
||||
<Flex w='100%' align="center">
|
||||
<Loader />
|
||||
<Flex w='100%' h="40dvh" justify="center" align="flex-end">
|
||||
<Loader size='xl' />
|
||||
</Flex>
|
||||
</Layout>
|
||||
),
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Text,
|
||||
Group,
|
||||
Stack,
|
||||
Paper,
|
||||
Indicator,
|
||||
Box,
|
||||
} from "@mantine/core";
|
||||
import { Text, Group, Stack, Paper, Indicator, Box } from "@mantine/core";
|
||||
import { CrownIcon } from "@phosphor-icons/react";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { Match } from "../types";
|
||||
@@ -46,7 +39,13 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
offset={2}
|
||||
>
|
||||
<Box style={{ position: "relative" }}>
|
||||
<Paper px="md" py="md" withBorder radius="md" style={{ position: "relative", zIndex: 2 }}>
|
||||
<Paper
|
||||
px="md"
|
||||
py="md"
|
||||
withBorder
|
||||
radius="md"
|
||||
style={{ position: "relative", zIndex: 2 }}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs">
|
||||
<Text size="xs" fw={600} lineClamp={1} c="dimmed">
|
||||
@@ -65,7 +64,16 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
style={{ position: "relative", cursor: "pointer" }}
|
||||
onClick={handleHomeTeamClick}
|
||||
>
|
||||
<Avatar size={40} name={match.home?.name!} radius="sm" />
|
||||
<Avatar
|
||||
size={40}
|
||||
name={match.home?.name!}
|
||||
radius="sm"
|
||||
src={
|
||||
match.home?.logo
|
||||
? `/api/files/teams/${match.home?.id}/${match.home?.logo}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{isHomeWin && (
|
||||
<Box
|
||||
style={{
|
||||
@@ -89,11 +97,7 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
{match.home?.name!}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text
|
||||
size="xl"
|
||||
fw={700}
|
||||
c={"dimmed"}
|
||||
>
|
||||
<Text size="xl" fw={700} c={"dimmed"}>
|
||||
{match.home_cups}
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -104,7 +108,16 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
style={{ position: "relative", cursor: "pointer" }}
|
||||
onClick={handleAwayTeamClick}
|
||||
>
|
||||
<Avatar size={40} name={match.away?.name!} radius="sm" />
|
||||
<Avatar
|
||||
size={40}
|
||||
name={match.away?.name!}
|
||||
radius="sm"
|
||||
src={
|
||||
match.away?.logo
|
||||
? `/api/files/teams/${match.away?.id}/${match.away?.logo}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{isAwayWin && (
|
||||
<Box
|
||||
style={{
|
||||
@@ -128,11 +141,7 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
{match.away?.name}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text
|
||||
size="xl"
|
||||
fw={700}
|
||||
c={"dimmed"}
|
||||
>
|
||||
<Text size="xl" fw={700} c={"dimmed"}>
|
||||
{match.away_cups}
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -149,7 +158,8 @@ const MatchCard = ({ match }: MatchCardProps) => {
|
||||
paddingTop: 8,
|
||||
backgroundColor: "var(--mantine-color-gray-light)",
|
||||
borderTop: "none",
|
||||
borderRadius: "0 0 var(--mantine-radius-md) var(--mantine-radius-md)",
|
||||
borderRadius:
|
||||
"0 0 var(--mantine-radius-md) var(--mantine-radius-md)",
|
||||
border: "1px solid var(--mantine-color-default-border)",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -15,6 +15,8 @@ const Profile = ({ id }: ProfileProps) => {
|
||||
const { data: matches } = usePlayerMatches(id);
|
||||
const { data: stats, isLoading: statsLoading } = usePlayerStats(id);
|
||||
|
||||
console.log(player.teams)
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Overview",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
Group,
|
||||
UnstyledButton,
|
||||
Button,
|
||||
Text,
|
||||
Tabs,
|
||||
} from "@mantine/core";
|
||||
@@ -71,61 +71,34 @@ const EmojiBar = ({
|
||||
<Group gap="xs" wrap="wrap" justify="space-between">
|
||||
<Group gap="xs" wrap="wrap">
|
||||
{reactions.map((reaction) => (
|
||||
<UnstyledButton
|
||||
<Button
|
||||
key={reaction.emoji}
|
||||
variant={reaction.hasReacted ? "filled" : "light"}
|
||||
color={reaction.hasReacted ? "var(--mantine-primary-color-filled)" : "gray"}
|
||||
size="compact-xs"
|
||||
radius="xl"
|
||||
onMouseDown={() => handleLongPressStart(reaction.emoji)}
|
||||
onMouseUp={handleLongPressEnd}
|
||||
onMouseLeave={handleLongPressEnd}
|
||||
onTouchStart={() => handleLongPressStart(reaction.emoji)}
|
||||
onTouchEnd={handleLongPressEnd}
|
||||
onClick={() => handleReactionClick(reaction.emoji)}
|
||||
px="8px"
|
||||
py="10px"
|
||||
style={{
|
||||
borderRadius: "var(--mantine-radius-xl)",
|
||||
border: reaction.hasReacted
|
||||
? "1px solid var(--mantine-primary-color-filled)"
|
||||
: "1px solid var(--mantine-color-default-border)",
|
||||
backgroundColor: reaction.hasReacted
|
||||
? "var(--mantine-primary-color-light)"
|
||||
: "transparent",
|
||||
transition: "all 0.15s ease",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
}}
|
||||
styles={{
|
||||
root: {
|
||||
"&:hover": {
|
||||
backgroundColor: reaction.hasReacted
|
||||
? "var(--mantine-primary-color-light)"
|
||||
: "var(--mantine-color-gray-1)",
|
||||
transform: "scale(1.05)",
|
||||
},
|
||||
"&:active": {
|
||||
transform: "scale(0.95)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Group gap={4} align="center">
|
||||
<Text size="10px" style={{ lineHeight: 1 }}>
|
||||
{reaction.emoji}
|
||||
</Text>
|
||||
<Text
|
||||
size="10px"
|
||||
fw={700}
|
||||
c={
|
||||
reaction.hasReacted
|
||||
? "var(--mantine-primary-color-filled)"
|
||||
: "dimmed"
|
||||
}
|
||||
>
|
||||
<Text size="10px" fw={600}>
|
||||
{reaction.count}
|
||||
</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</Button>
|
||||
))}
|
||||
</Group>
|
||||
<EmojiPicker onSelect={onReactionPress || (() => {})} />
|
||||
|
||||
18
src/features/teams/components/team-card-skeleton.tsx
Normal file
18
src/features/teams/components/team-card-skeleton.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Paper, Group, Skeleton, Box } from "@mantine/core";
|
||||
|
||||
const TeamCardSkeleton = () => {
|
||||
return (
|
||||
<Paper p="sm" withBorder radius="md">
|
||||
<Group gap="sm" align="center">
|
||||
<Skeleton height={32} width={32} radius="sm" />
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Skeleton height={16} width="70%" mb={4} />
|
||||
<Skeleton height={12} width="40%" />
|
||||
</Box>
|
||||
<Skeleton height={20} width={60} radius="xl" />
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamCardSkeleton;
|
||||
78
src/features/teams/components/team-card.tsx
Normal file
78
src/features/teams/components/team-card.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
Paper,
|
||||
Text,
|
||||
Group,
|
||||
Box,
|
||||
Stack,
|
||||
Divider
|
||||
} from "@mantine/core";
|
||||
import { useTeam } from "../queries";
|
||||
import Avatar from "@/components/avatar";
|
||||
import SongSummary from "./team-form/song-summary";
|
||||
|
||||
interface TeamCardProps {
|
||||
teamId: string;
|
||||
}
|
||||
|
||||
const TeamCard = ({ teamId }: TeamCardProps) => {
|
||||
const { data: team, error } = useTeam(teamId);
|
||||
|
||||
console.log(team)
|
||||
|
||||
if (error || !team) {
|
||||
return (
|
||||
<Paper p="sm" withBorder radius="md">
|
||||
<Text c="red" ta="center" size="sm">
|
||||
Failed to load team
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const song = {
|
||||
song_id: team.song_id,
|
||||
song_name: team.song_name,
|
||||
song_artist: team.song_artist,
|
||||
song_album: team.song_album,
|
||||
song_start: team.song_start,
|
||||
song_end: team.song_end,
|
||||
song_image_url: team.song_image_url,
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
withBorder
|
||||
radius="lg"
|
||||
shadow="xs"
|
||||
>
|
||||
<Stack gap={2}>
|
||||
<Group gap="md" align="center" p="xs">
|
||||
<Avatar
|
||||
name={team.name}
|
||||
size={40}
|
||||
radius="md"
|
||||
src={team.logo ? `/api/files/teams/${team.id}/${team.logo}` : undefined}
|
||||
style={{
|
||||
backgroundColor: team.primary_color || undefined,
|
||||
color: team.accent_color || undefined,
|
||||
}}
|
||||
/>
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="md" fw={600} lineClamp={1} mb={2}>
|
||||
{team.name}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" lineClamp={1}>
|
||||
{team.players?.map(p => `${p.first_name} ${p.last_name}`).join(', ')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
<Divider />
|
||||
<Box p="sm">
|
||||
<SongSummary song={song} />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamCard;
|
||||
@@ -21,6 +21,7 @@ interface TeamFormProps {
|
||||
initialValues?: Partial<TeamInput>;
|
||||
teamId?: string;
|
||||
tournamentId?: string;
|
||||
onSubmit?: (teamId: string) => void;
|
||||
}
|
||||
|
||||
const TeamForm = ({
|
||||
@@ -28,6 +29,7 @@ const TeamForm = ({
|
||||
initialValues,
|
||||
teamId,
|
||||
tournamentId,
|
||||
onSubmit
|
||||
}: TeamFormProps) => {
|
||||
const isEditMode = !!teamId;
|
||||
|
||||
@@ -103,7 +105,13 @@ const TeamForm = ({
|
||||
: "Failed to create team";
|
||||
|
||||
mutation(teamData, {
|
||||
onSuccess: async (team) => {
|
||||
onSuccess: async (team: any) => {
|
||||
console.log(team)
|
||||
queryClient.invalidateQueries({ queryKey: teamKeys.list });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: teamKeys.details(team.id),
|
||||
});
|
||||
|
||||
if (logo && team) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -122,10 +130,8 @@ const TeamForm = ({
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: teamKeys.list });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: teamKeys.details(result.team!.id),
|
||||
});
|
||||
console.log('here for some reason', result)
|
||||
|
||||
queryClient.setQueryData(
|
||||
tournamentKeys.details(result.team!.id),
|
||||
result.team
|
||||
@@ -137,7 +143,10 @@ const TeamForm = ({
|
||||
toast.error(logoErrorMessage);
|
||||
logger.error("Team logo upload error", error);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (team && team.id) {
|
||||
onSubmit?.(team.id)
|
||||
}
|
||||
close();
|
||||
},
|
||||
|
||||
@@ -16,7 +16,6 @@ interface SongSummaryProps {
|
||||
}
|
||||
|
||||
const SongSummary = ({ song }: SongSummaryProps) => {
|
||||
// Format time helper
|
||||
const formatTime = (seconds: number | undefined) => {
|
||||
if (seconds === undefined) return null;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
@@ -24,7 +23,6 @@ const SongSummary = ({ song }: SongSummaryProps) => {
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// If no song selected
|
||||
if (!song?.song_name) {
|
||||
return (
|
||||
<Group gap="xs" c="dimmed">
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Divider, Group, List, ListItem, Skeleton, Stack, Text } from "@mantine/core";
|
||||
import {
|
||||
Divider,
|
||||
Group,
|
||||
List,
|
||||
ListItem,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import Avatar from "@/components/avatar";
|
||||
import { TeamInfo } from "@/features/teams/types";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
@@ -15,14 +23,14 @@ const TeamListItem = React.memo(({ team }: TeamListItemProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Group justify="space-between" w='100%'>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Text fw={500}>{`${team.name}`}</Text>
|
||||
<Stack ml="auto" gap={0}>
|
||||
{playerNames.map((name) => (
|
||||
<Text size="xs" c="dimmed" ta="right">
|
||||
{name}
|
||||
</Text>
|
||||
))}
|
||||
<Text size="xs" c="dimmed" ta="right">
|
||||
{name}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
@@ -62,12 +70,23 @@ const TeamList = ({ teams, loading = false }: TeamListProps) => {
|
||||
<div key={team.id}>
|
||||
<ListItem
|
||||
p="xs"
|
||||
icon={<Avatar radius="sm" size={40} name={`${team.name}`} />}
|
||||
icon={
|
||||
<Avatar
|
||||
radius="sm"
|
||||
size={40}
|
||||
name={`${team.name}`}
|
||||
src={
|
||||
team.logo
|
||||
? `/api/files/teams/${team.id}/${team.logo}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => handleClick(team.id)}
|
||||
styles={{
|
||||
itemWrapper: { width: "100%" },
|
||||
itemLabel: { width: "100%" }
|
||||
itemLabel: { width: "100%" },
|
||||
}}
|
||||
w="100%"
|
||||
>
|
||||
|
||||
@@ -5,13 +5,19 @@ import { Team } from "../../types";
|
||||
interface HeaderProps {
|
||||
name: string;
|
||||
logo?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const Header = ({ name, logo }: HeaderProps) => {
|
||||
const Header = ({ name, logo, id }: HeaderProps) => {
|
||||
return (
|
||||
<>
|
||||
<Flex px="xl" w="100%" align="self-end" gap="md">
|
||||
<Avatar radius="sm" name={name} size={125} />
|
||||
<Avatar
|
||||
radius="sm"
|
||||
name={name}
|
||||
size={125}
|
||||
src={logo && id ? `/api/files/teams/${id}/${logo}` : undefined}
|
||||
/>
|
||||
<Flex align="center" justify="center" gap={4} pb={20} w="100%">
|
||||
<Title ta="center" order={2}>
|
||||
{name}
|
||||
|
||||
@@ -36,7 +36,7 @@ const TeamProfile = ({ id }: ProfileProps) => {
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Header name={team.name} logo={team.logo} />
|
||||
<Header name={team.name} logo={team.logo} id={team.id} />
|
||||
<Box mt="lg">
|
||||
<SwipeableTabs tabs={tabs} />
|
||||
</Box>
|
||||
|
||||
@@ -12,9 +12,6 @@ const useCreateTeam = () => {
|
||||
onMutate: (data) => {
|
||||
logger.info('Creating team', data);
|
||||
},
|
||||
onSuccess: (team) => {
|
||||
navigate({ to: '/teams/$id', params: { id: team.id } });
|
||||
},
|
||||
successMessage: 'Team created successfully!',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -34,7 +34,15 @@ const TeamItem = memo(({ team, onUnenroll, disabled }: TeamItemProps) => {
|
||||
|
||||
return (
|
||||
<Group py="xs" px="sm" w="100%" gap="sm" align="center">
|
||||
<Avatar size={32} name={team.name} />
|
||||
<Avatar
|
||||
size={32}
|
||||
name={team.name}
|
||||
src={
|
||||
team.logo
|
||||
? `/api/files/teams/${team.id}/${team.logo}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} truncate>
|
||||
{team.name}
|
||||
|
||||
@@ -27,11 +27,11 @@ interface SeedTournamentProps {
|
||||
|
||||
const SeedTournament: React.FC<SeedTournamentProps> = ({
|
||||
tournamentId,
|
||||
teams
|
||||
teams,
|
||||
}) => {
|
||||
const [orderedTeams, setOrderedTeams] = useState<TeamInfo[]>(teams);
|
||||
const { data: bracketPreview } = useBracketPreview(teams.length);
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const bracket: BracketData = useMemo(
|
||||
() => ({
|
||||
@@ -48,7 +48,7 @@ const SeedTournament: React.FC<SeedTournamentProps> = ({
|
||||
: undefined,
|
||||
}))
|
||||
),
|
||||
losers: bracketPreview.losers
|
||||
losers: bracketPreview.losers,
|
||||
}),
|
||||
[bracketPreview, orderedTeams]
|
||||
);
|
||||
@@ -58,8 +58,8 @@ const SeedTournament: React.FC<SeedTournamentProps> = ({
|
||||
successMessage: "Tournament bracket generated successfully!",
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: tournamentKeys.details(tournamentId)
|
||||
})
|
||||
queryKey: tournamentKeys.details(tournamentId),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ const SeedTournament: React.FC<SeedTournamentProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: '2rem', alignItems: 'flex-start' }}>
|
||||
<div style={{ display: "flex", gap: "2rem", alignItems: "flex-start" }}>
|
||||
<Stack gap="lg" style={{ flexShrink: 0 }}>
|
||||
<Stack gap={0} pos="relative" w={400}>
|
||||
<LoadingOverlay visible={generateBracket.isPending} />
|
||||
@@ -171,7 +171,16 @@ const SeedTournament: React.FC<SeedTournamentProps> = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<Avatar size={24} radius="sm" name={team.name} />
|
||||
<Avatar
|
||||
size={24}
|
||||
radius="sm"
|
||||
name={team.name}
|
||||
src={
|
||||
team.logo
|
||||
? `/api/files/teams/${team.id}/${team.logo}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<Text fw={500} size="sm" style={{ flex: 1 }}>
|
||||
{team.name}
|
||||
@@ -198,7 +207,7 @@ const SeedTournament: React.FC<SeedTournamentProps> = ({
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
<div style={{ flex: 1, overflow: "auto" }}>
|
||||
<BracketView bracket={bracket} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,16 @@ import TeamForm from "@/features/teams/components/team-form";
|
||||
import { teamQueries } from "@/features/teams/queries";
|
||||
import { Team } from "@/features/teams/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Loader } from "@mantine/core";
|
||||
import useEnrollTeam from "@/features/tournaments/hooks/use-enroll-team";
|
||||
import { useServerQuery } from "@/lib/tanstack-query/hooks";
|
||||
|
||||
interface EnrollTeamProps {
|
||||
tournamentId: string;
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
const EnrollTeam = ({ tournamentId }: EnrollTeamProps) => {
|
||||
const EnrollTeam = ({ tournamentId, onSubmit }: EnrollTeamProps) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
const { user } = useAuth();
|
||||
|
||||
@@ -22,14 +26,14 @@ const EnrollTeam = ({ tournamentId }: EnrollTeamProps) => {
|
||||
const [showTeamForm, setShowTeamForm] = useState<boolean>(!hasTeams);
|
||||
const [selectedTeam, setSelectedTeam] = useState<Team | null>(null);
|
||||
|
||||
const { data: teamData } = useQuery({
|
||||
const { data: teamData } = useServerQuery({
|
||||
...teamQueries.details(selectedTeamId!),
|
||||
enabled: !!selectedTeamId,
|
||||
options: { enabled: !!selectedTeamId }
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (teamData?.success) {
|
||||
setSelectedTeam(teamData.data);
|
||||
if (teamData) {
|
||||
setSelectedTeam(teamData);
|
||||
setShowTeamForm(true);
|
||||
}
|
||||
}, [teamData]);
|
||||
@@ -71,6 +75,16 @@ const EnrollTeam = ({ tournamentId }: EnrollTeamProps) => {
|
||||
};
|
||||
}, [selectedTeam]);
|
||||
|
||||
const { mutate: enrollTeam, isPending: isEnrolling } = useEnrollTeam();
|
||||
const handleEnrollTeam = (teamId: string) => {
|
||||
enrollTeam({ tournamentId, teamId }, {
|
||||
onSuccess: () => {
|
||||
toggle();
|
||||
onSubmit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" onClick={open}>
|
||||
@@ -78,12 +92,14 @@ const EnrollTeam = ({ tournamentId }: EnrollTeamProps) => {
|
||||
</Button>
|
||||
|
||||
<Sheet title={showTeamForm ? "Team Details" : "Enroll Team"} opened={isOpen} onChange={toggle}>
|
||||
{showTeamForm ? (
|
||||
{ isEnrolling && <Loader size="lg" /> }
|
||||
{showTeamForm && !isEnrolling ? (
|
||||
<TeamForm
|
||||
close={handleBack}
|
||||
tournamentId={tournamentId}
|
||||
initialValues={initialValues}
|
||||
teamId={selectedTeamId}
|
||||
onSubmit={handleEnrollTeam}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import { useMemo } from "react";
|
||||
import { Suspense, useCallback, useMemo } from "react";
|
||||
import { Tournament } from "../../types";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
Divider,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Box, Button, Card, Divider, Group, Stack, Text } from "@mantine/core";
|
||||
import Countdown from "@/components/countdown";
|
||||
import ListLink from "@/components/list-link";
|
||||
import ListButton from "@/components/list-button";
|
||||
import {
|
||||
UsersIcon,
|
||||
ListIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { UsersIcon, ListIcon } from "@phosphor-icons/react";
|
||||
import EnrollTeam from "./enroll-team";
|
||||
import EnrollFreeAgent from "./enroll-free-agent";
|
||||
import TeamListButton from "./team-list-button";
|
||||
import Header from "./header";
|
||||
import EmojiPicker from "@/features/reactions/components/emoji-picker";
|
||||
import EmojiBar from "@/features/reactions/components/emoji-bar";
|
||||
import TeamCardSkeleton from "@/features/teams/components/team-card-skeleton";
|
||||
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";
|
||||
|
||||
const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
tournament,
|
||||
@@ -43,18 +37,21 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
: new Date(tournament.start_time);
|
||||
const isEnrollmentOpen = enrollmentDeadline > new Date();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const handleSubmit = () => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.current })
|
||||
}
|
||||
|
||||
console.log(userTeam)
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
|
||||
<Header tournament={tournament} />
|
||||
|
||||
<EmojiBar />
|
||||
|
||||
{tournament.desc && <Text size="sm">{tournament.desc}</Text>}
|
||||
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Card withBorder radius="lg" p="lg">
|
||||
<Stack gap="xs">
|
||||
<Group mb='sm' gap="xs" align="center">
|
||||
<Group mb="sm" gap="xs" align="center">
|
||||
<UsersIcon size={16} />
|
||||
<Text size="sm" fw={500}>
|
||||
Enrollment
|
||||
@@ -70,17 +67,29 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{!isEnrollmentOpen && !isUserEnrolled && (
|
||||
<Text fw={600} c="dimmed" size="sm">Enrollment has been closed for this tournament.</Text>
|
||||
{!isUserEnrolled &&!isEnrollmentOpen && (
|
||||
<Text fw={600} c="dimmed" size="sm">
|
||||
Enrollment has been closed for this tournament.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{!isUserEnrolled && isEnrollmentOpen && (
|
||||
{!isUserEnrolled &&isEnrollmentOpen && (
|
||||
<>
|
||||
<EnrollTeam tournamentId={tournament.id} />
|
||||
<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} />}
|
||||
</>
|
||||
}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import Button from "@/components/button";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { useCallback } from "react";
|
||||
import useUnenrollTeam from "../../hooks/use-unenroll-team";
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
|
||||
interface UnenrollTeamProps {
|
||||
tournamentId: string;
|
||||
teamId: string
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
const UnenrollTeam = ({ tournamentId, teamId, onSubmit }: UnenrollTeamProps) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
const { mutate: unenrollTeam } = useUnenrollTeam();
|
||||
const handleUnenrollTeam = useCallback(
|
||||
async () => {
|
||||
await unenrollTeam({ tournamentId, teamId }, {
|
||||
onSuccess: () => {
|
||||
toggle();
|
||||
onSubmit();
|
||||
}
|
||||
});
|
||||
},
|
||||
[unenrollTeam, tournamentId]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" onClick={open} variant="subtle">
|
||||
Unenroll
|
||||
</Button>
|
||||
|
||||
<Sheet title={"Unenroll Team"} opened={isOpen} onChange={toggle}>
|
||||
<Stack gap='xs'>
|
||||
<Text size="sm" fw={500}>Are you sure you want to unenroll from this tournament? You can enroll again at any point before the deadline.</Text>
|
||||
<Button onClick={handleUnenrollTeam}>Confirm</Button>
|
||||
<Button variant="subtle" color="red" onClick={toggle}>Cancel</Button>
|
||||
</Stack>
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnenrollTeam;
|
||||
@@ -0,0 +1,59 @@
|
||||
import Button from "@/components/button";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { useMemo, useState, useCallback, useEffect } from "react";
|
||||
import TeamSelectionView from "./enroll-team/team-selection-view";
|
||||
import TeamForm from "@/features/teams/components/team-form";
|
||||
import { teamQueries, useTeam } from "@/features/teams/queries";
|
||||
import { Team } from "@/features/teams/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Loader } from "@mantine/core";
|
||||
import useEnrollTeam from "@/features/tournaments/hooks/use-enroll-team";
|
||||
|
||||
interface UpdateTeamProps {
|
||||
tournamentId: string;
|
||||
teamId: string
|
||||
}
|
||||
|
||||
const UpdateTeam = ({ tournamentId, teamId }: UpdateTeamProps) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
|
||||
const { data: team } = useTeam(teamId);
|
||||
|
||||
const initialValues = useMemo(() => {
|
||||
if (!team) return undefined;
|
||||
|
||||
return {
|
||||
name: team.name,
|
||||
song_id: team.song_id,
|
||||
song_name: team.song_name,
|
||||
song_artist: team.song_artist,
|
||||
song_album: team.song_album,
|
||||
song_start: team.song_start,
|
||||
song_end: team.song_end,
|
||||
song_image_url: team.song_image_url,
|
||||
players: team.players?.map(player => player.id),
|
||||
};
|
||||
}, [team]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" onClick={open}>
|
||||
Update Team
|
||||
</Button>
|
||||
|
||||
<Sheet title={"Update Team"} opened={isOpen} onChange={toggle}>
|
||||
<TeamForm
|
||||
close={toggle}
|
||||
tournamentId={tournamentId}
|
||||
initialValues={initialValues}
|
||||
teamId={teamId}
|
||||
onSubmit={(_) => close()}
|
||||
/>
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateTeam;
|
||||
@@ -22,6 +22,7 @@ export function transformTeamInfo(record: any): TeamInfo {
|
||||
primary_color: record.primary_color,
|
||||
accent_color: record.accent_color,
|
||||
players,
|
||||
logo: record.logo
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,7 +109,7 @@ export function transformTeam(record: any): Team {
|
||||
song_artist: record.song_artist,
|
||||
song_album: record.song_album,
|
||||
song_start: record.song_start,
|
||||
song_end: 0,
|
||||
song_end: record.song_end,
|
||||
song_image_url: record.song_image_url,
|
||||
created: record.created,
|
||||
updated: record.updated,
|
||||
|
||||
Reference in New Issue
Block a user