From 4487a559817c0c7129a4a827305dad9a372ebf43 Mon Sep 17 00:00:00 2001 From: Harshith Mohan <26010946+harshithmohan@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:51:39 +0530 Subject: [PATCH] Add voting for series (#984) --- .../Collection/Series/SeriesRating.tsx | 69 +++++++++++++++++++ src/components/Collection/SeriesUserStats.tsx | 10 +-- src/core/react-query/series/mutations.ts | 9 +++ src/core/types/api/common.ts | 2 +- 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/components/Collection/Series/SeriesRating.tsx diff --git a/src/components/Collection/Series/SeriesRating.tsx b/src/components/Collection/Series/SeriesRating.tsx new file mode 100644 index 000000000..49aa5afce --- /dev/null +++ b/src/components/Collection/Series/SeriesRating.tsx @@ -0,0 +1,69 @@ +import React, { useState } from 'react'; +import { mdiStar, mdiStarOutline } from '@mdi/js'; +import { Icon } from '@mdi/react'; +import { toNumber } from 'lodash'; + +import toast from '@/components/Toast'; +import { useVoteSeriesMutation } from '@/core/react-query/series/mutations'; +import useEventCallback from '@/hooks/useEventCallback'; + +type Props = { + seriesId: number; + ratingValue: number; +}; + +type StarIconProps = { + hovered: boolean; + index: string; + handleHover: (event: React.MouseEvent) => void; + handleVote: (event: React.MouseEvent) => void; +}; + +const StarIcon = React.memo(({ handleHover, handleVote, hovered, index }: StarIconProps) => ( +
+ +
+)); + +const SeriesRating = ({ ratingValue, seriesId }: Props) => { + const { mutate: voteSeries } = useVoteSeriesMutation(); + + const [hoveredStar, setHoveredStar] = useState(ratingValue); + + const handleVote = useEventCallback((event: React.MouseEvent) => { + voteSeries({ seriesId, rating: toNumber(event.currentTarget.id) }, { + onSuccess: () => toast.success('Voted!'), + onError: () => toast.error('Failed to vote!'), + }); + }); + + const handleClear = useEventCallback(() => { + setHoveredStar(ratingValue); + }); + + const handleHover = useEventCallback((event: React.MouseEvent) => { + setHoveredStar(toNumber(event.currentTarget.id)); + }); + + return ( +
+ {Array.from({ length: 10 }).map((_, index) => ( + = index} + handleHover={handleHover} + handleVote={handleVote} + /> + ))} +
+ ); +}; + +export default SeriesRating; diff --git a/src/components/Collection/SeriesUserStats.tsx b/src/components/Collection/SeriesUserStats.tsx index ea72cdce5..2b344c831 100644 --- a/src/components/Collection/SeriesUserStats.tsx +++ b/src/components/Collection/SeriesUserStats.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import SeriesRating from '@/components/Collection/Series/SeriesRating'; import { formatThousand } from '@/core/util'; import type { SeriesType } from '@/core/types/api/series'; @@ -75,11 +76,12 @@ const SeriesUserStats = React.memo(({ series }: SeriesInfoProps) => ( :
None, Nice Work!
} -
-
Series Rating
-
- N/A (Not Implemented Yet) +
+
+ Series Rating  + {series.UserRating.Type === 'Temporary' && '(Temp)'}
+
)); diff --git a/src/core/react-query/series/mutations.ts b/src/core/react-query/series/mutations.ts index 71a710dd8..457c623ca 100644 --- a/src/core/react-query/series/mutations.ts +++ b/src/core/react-query/series/mutations.ts @@ -102,6 +102,15 @@ export const useRescanSeriesFilesMutation = () => mutationFn: (seriesId: number) => axios.post(`Series/${seriesId}/File/Rescan`), }); +export const useVoteSeriesMutation = () => + useMutation({ + mutationFn: ({ rating, seriesId }: { seriesId: number, rating: number }) => + axios.post(`Series/${seriesId}/Vote`, { Value: rating, MaxValue: 10 }), + onSuccess: (_, { seriesId }) => { + invalidateQueries(['series', seriesId, 'data']); + }, + }); + export const useWatchSeriesEpisodesMutation = () => useMutation({ mutationFn: ({ seriesId, ...params }: WatchSeriesEpisodesRequestType) => diff --git a/src/core/types/api/common.ts b/src/core/types/api/common.ts index 7f490ab39..e1dce0116 100644 --- a/src/core/types/api/common.ts +++ b/src/core/types/api/common.ts @@ -51,7 +51,7 @@ export type RatingType = { MaxValue: number; Source: string; Votes: number; - Type: string | null; + Type: 'Permanent' | 'Temporary'; }; export type FilterType = {