diff --git a/src/components/PlayerVote.tsx b/src/components/PlayerVote.tsx new file mode 100644 index 0000000..9f8743d --- /dev/null +++ b/src/components/PlayerVote.tsx @@ -0,0 +1,74 @@ +import queryClient from '@api/queryClient'; +import { useAuth } from '@contexts/AuthContext'; +import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/24/outline'; +import usePlayerVoteMutation from '@hooks/mutations/usePlayerVoteMutation'; +import usePlayerTotalVotesQuery from '@hooks/queries/usePlayerTotalVotesQuery'; +import usePlayerVoteQuery from '@hooks/queries/usePlayerVoteQuery'; +import toast from 'react-hot-toast/headless'; + +interface PlayerVoteProps { + playerId: string; +} + +const PlayerVote = ({ playerId }: PlayerVoteProps) => { + const { user } = useAuth(); + + const { mutateAsync } = usePlayerVoteMutation(); + + const { data: playerVote } = usePlayerVoteQuery(playerId); + const { data: totalVotes } = usePlayerTotalVotesQuery(playerId); + + const handleVote = async (vote: number) => { + try { + await mutateAsync({ playerId, vote }).then(() => { + queryClient.setQueryData(['playerVote', playerId, user?.id], vote); + queryClient.invalidateQueries(['playerTotalVotes', playerId]); + }); + } catch (error) { + if (error instanceof Error) { + toast('Something went wrong!'); + return; + } + } + }; + + const isPlayerMe = user?.id === playerId; + + return ( +
+ {!isPlayerMe && ( + + )} +

0 + ? 'text-green-500' + : totalVotes && totalVotes < 0 + ? 'text-red-500' + : '' + }`} + > + {totalVotes ?? 0} +

+ {!isPlayerMe && ( + + )} +
+ ); +}; + +export default PlayerVote; diff --git a/src/hooks/mutations/usePlayerVoteMutation.ts b/src/hooks/mutations/usePlayerVoteMutation.ts new file mode 100644 index 0000000..32b3e55 --- /dev/null +++ b/src/hooks/mutations/usePlayerVoteMutation.ts @@ -0,0 +1,30 @@ +import { supabase } from '@api/supabase'; +import { useAuth } from '@contexts/AuthContext'; +import { useMutation } from '@tanstack/react-query'; + +interface PlayerVoteMutationProps { + playerId: string; + vote: number; +} + +const usePlayerVoteMutation = () => { + const { user } = useAuth(); + + return useMutation(async ({ playerId, vote }: PlayerVoteMutationProps) => { + if (!user) { + throw new Error('You must be logged in to do this.'); + } + + const { data, error } = await supabase + .from('player_votes') + .upsert({ user_id: user.id, player_id: playerId, vote }); + + if (error) { + throw new Error(error.message); + } + + return data; + }); +}; + +export default usePlayerVoteMutation; diff --git a/src/hooks/queries/usePlayerTotalVotesQuery.ts b/src/hooks/queries/usePlayerTotalVotesQuery.ts new file mode 100644 index 0000000..f259aa0 --- /dev/null +++ b/src/hooks/queries/usePlayerTotalVotesQuery.ts @@ -0,0 +1,17 @@ +import { supabase } from '@api/supabase'; +import { useQuery } from '@tanstack/react-query'; + +const usePlayerTotalVotesQuery = (playerId: string) => { + const queryKey = ['playerTotalVotes', playerId]; + + return useQuery(queryKey, async () => { + const { data } = await supabase.rpc('get_player_votes', { + var_player_id: playerId, + }); + console.log(data); + + return data ?? 0; + }); +}; + +export default usePlayerTotalVotesQuery; diff --git a/src/hooks/queries/usePlayerVoteQuery.ts b/src/hooks/queries/usePlayerVoteQuery.ts new file mode 100644 index 0000000..3f7a131 --- /dev/null +++ b/src/hooks/queries/usePlayerVoteQuery.ts @@ -0,0 +1,26 @@ +import { supabase } from '@api/supabase'; +import { useAuth } from '@contexts/AuthContext'; +import { useQuery } from '@tanstack/react-query'; + +const usePlayerVoteQuery = (playerId: string) => { + const { user } = useAuth(); + + const queryKey = ['playerVote', playerId, user?.id]; + + return useQuery(queryKey, async () => { + if (!user) { + return null; + } + + const { data } = await supabase + .from('player_votes') + .select('vote') + .eq('user_id', user.id) + .eq('player_id', playerId) + .single(); + + return data?.vote ?? null; + }); +}; + +export default usePlayerVoteQuery; diff --git a/src/pages/Player.tsx b/src/pages/Player.tsx index 92cf31e..8a837b8 100644 --- a/src/pages/Player.tsx +++ b/src/pages/Player.tsx @@ -3,6 +3,7 @@ import { format } from 'date-fns'; import usePlayerQuery from '@hooks/queries/usePlayerQuery'; import Stats from '@components/Stats'; +import PlayerVote from '@components/PlayerVote'; const Player = () => { const { username = '' } = useParams(); @@ -18,18 +19,23 @@ const Player = () => { } return ( -
-
-

{username}

-
-

- {'Member since: '} - - {format(new Date(player.created_at), 'd MMM yyyy')} - -

+
+
+
+
+

{username}

+ +
+
+

+ {'Member since: '} + + {format(new Date(player.created_at), 'd MMM yyyy')} + +

+
+ {player.stats && }
- {player.stats && }
); }; diff --git a/src/types/supabase.ts b/src/types/supabase.ts index 2e0f595..1852a79 100644 --- a/src/types/supabase.ts +++ b/src/types/supabase.ts @@ -94,6 +94,40 @@ export interface Database { }, ]; }; + player_votes: { + Row: { + created_at: string; + player_id: string; + user_id: string; + vote: number; + }; + Insert: { + created_at?: string; + player_id: string; + user_id?: string; + vote?: number; + }; + Update: { + created_at?: string; + player_id?: string; + user_id?: string; + vote?: number; + }; + Relationships: [ + { + foreignKeyName: 'player_votes_player_id_fkey'; + columns: ['player_id']; + referencedRelation: 'users'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'player_votes_user_id_fkey'; + columns: ['user_id']; + referencedRelation: 'users'; + referencedColumns: ['id']; + }, + ]; + }; users: { Row: { created_at: string; @@ -136,7 +170,12 @@ export interface Database { [_ in never]: never; }; Functions: { - [_ in never]: never; + get_player_votes: { + Args: { + var_player_id: string; + }; + Returns: number; + }; }; Enums: { group_status_enum: 'open' | 'closed';