Documentation is currently in beta. Report issues →
API ReferenceFavorites

Favorites API

Endpoints for managing user favorites (saved listings).

All favorites endpoints require authentication.

Toggle Favorite

Add or remove a listing from favorites.

POST /api/favorites/toggle

Request Body

{
  "listingId": "clx123abc..."
}

Response

{
  "data": {
    "favorited": true,
    "listing": {
      "id": "clx123abc...",
      "title": "Luxury Beach Villa"
    }
  }
}

If already favorited, removes it:

{
  "data": {
    "favorited": false,
    "listing": {
      "id": "clx123abc...",
      "title": "Luxury Beach Villa"
    }
  }
}

Get User Favorites

List all favorited listings for the authenticated user.

GET /api/favorites

Query Parameters

ParameterTypeDefaultDescription
pagenumber1Page number
limitnumber20Items per page

Response

{
  "data": [
    {
      "listingId": "clx123abc...",
      "createdAt": "2024-02-15T10:30:00Z",
      "listing": {
        "id": "clx123abc...",
        "title": "Luxury Beach Villa",
        "slug": "luxury-beach-villa-accra",
        "region": "Greater Accra",
        "city": "Accra",
        "priceGHS": "850.00",
        "bedrooms": 3,
        "maxGuests": 6,
        "images": [
          {
            "url": "https://res.cloudinary.com/...",
            "isPrimary": true
          }
        ]
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 5,
    "totalPages": 1,
    "hasMore": false
  }
}

Check if Favorited

Check if a specific listing is favorited.

GET /api/favorites/check/{listingId}

Response

{
  "data": {
    "favorited": true
  }
}

Server Action Implementation

// lib/actions/favorites.ts
"use server"
 
import { createClient } from '@/lib/supabase/server'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'
 
export async function toggleFavorite(listingId: string) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
 
  if (!user) {
    throw new Error('Please log in to save favorites')
  }
 
  // Check if already favorited
  const existing = await prisma.favorite.findUnique({
    where: {
      userId_listingId: {
        userId: user.id,
        listingId
      }
    }
  })
 
  if (existing) {
    // Remove favorite
    await prisma.favorite.delete({
      where: {
        userId_listingId: {
          userId: user.id,
          listingId
        }
      }
    })
    revalidatePath('/dashboard/favorites')
    return { favorited: false }
  } else {
    // Add favorite
    await prisma.favorite.create({
      data: {
        userId: user.id,
        listingId
      }
    })
    revalidatePath('/dashboard/favorites')
    return { favorited: true }
  }
}
 
export async function getUserFavorites() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
 
  if (!user) {
    return []
  }
 
  return prisma.favorite.findMany({
    where: { userId: user.id },
    include: {
      listing: {
        include: {
          images: {
            where: { isPrimary: true },
            take: 1
          }
        }
      }
    },
    orderBy: { createdAt: 'desc' }
  })
}
 
export async function isFavorited(listingId: string) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
 
  if (!user) return false
 
  const favorite = await prisma.favorite.findUnique({
    where: {
      userId_listingId: {
        userId: user.id,
        listingId
      }
    }
  })
 
  return !!favorite
}

Component Usage

Favorite Button

// components/features/favorites/favorite-button.tsx
"use client"
 
import { useState, useTransition } from 'react'
import { Heart } from 'lucide-react'
import { toggleFavorite } from '@/lib/actions/favorites'
 
interface FavoriteButtonProps {
  listingId: string
  initialFavorited: boolean
}
 
export function FavoriteButton({ listingId, initialFavorited }: FavoriteButtonProps) {
  const [favorited, setFavorited] = useState(initialFavorited)
  const [isPending, startTransition] = useTransition()
 
  function handleClick() {
    startTransition(async () => {
      const result = await toggleFavorite(listingId)
      setFavorited(result.favorited)
    })
  }
 
  return (
    <button
      onClick={handleClick}
      disabled={isPending}
      className="p-2 rounded-full bg-white/90 backdrop-blur-sm hover:bg-white transition-colors"
    >
      <Heart
        className={`w-5 h-5 ${
          favorited ? 'fill-red-500 text-red-500' : 'text-gray-600'
        }`}
      />
    </button>
  )
}

With Auth Check

// components/features/favorites/favorite-button-wrapper.tsx
import { FavoriteButton } from './favorite-button'
import { isFavorited } from '@/lib/actions/favorites'
import { getCurrentUser } from '@/lib/auth'
 
export async function FavoriteButtonWrapper({ listingId }: { listingId: string }) {
  const user = await getCurrentUser()
 
  if (!user) {
    // Show login prompt instead
    return <FavoriteButtonLogin />
  }
 
  const favorited = await isFavorited(listingId)
 
  return <FavoriteButton listingId={listingId} initialFavorited={favorited} />
}