1+ "use client" ;
2+
3+ import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
4+ import { addToUsersFavorites , deleteFromUsersFavorites } from "@/lib/actions" ;
15import { Heart , Share } from "lucide-react" ;
26import { Item , Sublet } from "@/lib/types" ;
37import { ListingActions } from "@/components/listings/detail/ListingActions" ;
@@ -8,20 +12,63 @@ import { BackButton } from "@/components/listings/detail/BackButton";
812
913interface Props {
1014 listing : Item | Sublet ;
15+ initialIsFavorited : boolean ;
1116}
1217
13- export const ListingDetail = ( { listing } : Props ) => {
18+ export const ListingDetail = ( { listing, initialIsFavorited } : Props ) => {
1419 const listingType = listing . listing_type ;
1520 const priceLabel = listingType === "sublet" ? "/mo" : undefined ;
1621 const listingOwnerLabel = listingType === "item" ? "Seller" : "Owner" ;
22+ const queryClient = useQueryClient ( ) ;
23+ const favoritesQuery = useQuery ( {
24+ queryKey : [ "favorite" , listing . id ] ,
25+ queryFn : async ( ) => initialIsFavorited ,
26+ initialData : initialIsFavorited ,
27+ staleTime : Infinity ,
28+ } ) ;
29+
30+ const isFavorited = favoritesQuery . data ?? false ;
31+
32+ const toggleFavoriteMutation = useMutation ( {
33+ mutationFn : async ( shouldFavorite : boolean ) => {
34+ if ( shouldFavorite ) {
35+ await addToUsersFavorites ( listing . id ) ;
36+ } else {
37+ await deleteFromUsersFavorites ( listing . id ) ;
38+ }
39+ } ,
40+ onMutate : async ( shouldFavorite : boolean ) => {
41+ await queryClient . cancelQueries ( { queryKey : [ "favorite" , listing . id ] } ) ;
42+ const previous = queryClient . getQueryData < boolean > ( [ "favorite" , listing . id ] ) ;
43+ queryClient . setQueryData ( [ "favorite" , listing . id ] , shouldFavorite ) ;
44+ return { previous } ;
45+ } ,
46+ onError : ( _error , _shouldFavorite , context ) => {
47+ if ( context ?. previous !== undefined ) {
48+ queryClient . setQueryData ( [ "favorite" , listing . id ] , context . previous ) ;
49+ }
50+ } ,
51+ } ) ;
52+
53+ const handleToggleFavorite = async ( ) => {
54+ toggleFavoriteMutation . mutate ( ! isFavorited ) ;
55+ } ;
1756
1857 return (
1958 < div className = "mx-auto flex w-full max-w-[96rem] flex-col p-8 px-4 sm:px-12" >
2059 < div className = "mb-4 flex items-center justify-between" >
2160 < BackButton />
2261 < div className = "flex items-center gap-3" >
2362 < Share className = "h-5 w-5" />
24- < Heart className = "h-5 w-5" />
63+ < button
64+ type = "button"
65+ className = "cursor-pointer"
66+ onClick = { handleToggleFavorite }
67+ aria-pressed = { isFavorited }
68+ aria-label = { isFavorited ? "Remove from favorites" : "Add to favorites" }
69+ >
70+ < Heart className = { isFavorited ? "h-5 w-5 fill-red-500 text-red-500" : "h-5 w-5" } />
71+ </ button >
2572 </ div >
2673 </ div >
2774 < div className = "grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" >
0 commit comments