diff --git a/components/home/Favorites.tsx b/components/home/Favorites.tsx index 88657a4c..adc15946 100644 --- a/components/home/Favorites.tsx +++ b/components/home/Favorites.tsx @@ -33,7 +33,7 @@ interface FavoritesProps { emptyTitleKey?: string; emptyTextKey?: string; /** Namespace for the see-all page headers ("favorites" or "kefintweaksWatchlist"). */ - seeAllNamespace?: string; + seeAllNamespace?: "kefintweaksWatchlist" | "favorites"; } export const Favorites = ({ @@ -143,71 +143,18 @@ export const Favorites = ({ [fetchFavoritesByType, pageSize], ); - const handleSeeAllSeries = useCallback(() => { - router.push({ - pathname: "/(auth)/(tabs)/(favorites)/see-all", - params: { - type: "Series", - title: t(`${seeAllNamespace}.seeAllSeries`), - filter, - }, - } as any); - }, [router, filter, seeAllNamespace]); - - const handleSeeAllMovies = useCallback(() => { - router.push({ - pathname: "/(auth)/(tabs)/(favorites)/see-all", - params: { - type: "Movie", - title: t(`${seeAllNamespace}.seeAllMovies`), - filter, - }, - } as any); - }, [router, filter, seeAllNamespace]); - - const handleSeeAllEpisodes = useCallback(() => { - router.push({ - pathname: "/(auth)/(tabs)/(favorites)/see-all", - params: { - type: "Episode", - title: t(`${seeAllNamespace}.seeAllEpisodes`), - filter, - }, - } as any); - }, [router, filter, seeAllNamespace]); - - const handleSeeAllVideos = useCallback(() => { - router.push({ - pathname: "/(auth)/(tabs)/(favorites)/see-all", - params: { - type: "Video", - title: t(`${seeAllNamespace}.seeAllVideos`), - filter, - }, - } as any); - }, [router, filter, seeAllNamespace]); - - const handleSeeAllBoxsets = useCallback(() => { - router.push({ - pathname: "/(auth)/(tabs)/(favorites)/see-all", - params: { - type: "BoxSet", - title: t(`${seeAllNamespace}.seeAllBoxsets`), - filter, - }, - } as any); - }, [router, filter, seeAllNamespace]); - - const handleSeeAllPlaylists = useCallback(() => { - router.push({ - pathname: "/(auth)/(tabs)/(favorites)/see-all", - params: { - type: "Playlist", - title: t(`${seeAllNamespace}.seeAllPlaylists`), - filter, - }, - } as any); - }, [router, filter, seeAllNamespace]); + // Navigate to the shared see-all screen. `titleKey` is the see-all header + // segment in the active namespace (e.g. "seeAllSeries"). The cast is needed + // because the route's custom params aren't part of expo-router's typed Href. + const seeAll = useCallback( + (type: FavoriteTypes, titleKey: string) => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type, title: t(`${seeAllNamespace}.${titleKey}`), filter }, + } as any); + }, + [router, filter, seeAllNamespace], + ); return ( @@ -233,7 +180,7 @@ export const Favorites = ({ title={t("favorites.series")} hideIfEmpty pageSize={pageSize} - onPressSeeAll={handleSeeAllSeries} + onPressSeeAll={() => seeAll("Series", "seeAllSeries")} /> seeAll("Movie", "seeAllMovies")} /> seeAll("Episode", "seeAllEpisodes")} /> seeAll("Video", "seeAllVideos")} /> seeAll("BoxSet", "seeAllBoxsets")} /> seeAll("Playlist", "seeAllPlaylists")} /> ); diff --git a/hooks/useWatchlist.ts b/hooks/useWatchlist.ts index 09da3aa3..eeff81d7 100644 --- a/hooks/useWatchlist.ts +++ b/hooks/useWatchlist.ts @@ -2,6 +2,7 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { atom, useAtom } from "jotai"; import { useCallback, useEffect, useMemo, useRef } from "react"; +import { toast } from "sonner-native"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; // Shared atom to store watchlist (Likes) status across all components @@ -92,7 +93,7 @@ export const useWatchlist = (item: BaseItemDto) => { const currentItem = itemRef.current; if (!currentApi || !currentUser?.Id || !currentItem?.Id) { - return; + throw new Error("Cannot update watchlist: not signed in"); } // Watchlist == Jellyfin "Likes" rating: @@ -120,13 +121,15 @@ export const useWatchlist = (item: BaseItemDto) => { return { previousIsWatchlisted, previousQueries }; }, - onError: (_err, _nextIsWatchlisted, context) => { + onError: (error: Error, _nextIsWatchlisted, context) => { + // Roll back the optimistic Likes flip applied in onMutate. if (context?.previousQueries) { for (const [queryKey, data] of context.previousQueries) { queryClient.setQueryData(queryKey, data); } } setIsWatchlisted(context?.previousIsWatchlisted); + toast.error(error.message || "Failed to update watchlist"); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: itemQueryKeyPrefix });