feat: rating stars on recipe cards (Search, Explore)

New lib/recipe-ratings.ts helper batches avg score + review count for
a set of recipe ids in one grouped query, kept separate from the main
paginated/sorted search query rather than joining+grouping it directly
(would've forced grouping by every selected column). Wired into
/api/v1/search and the Explore page's trending/recent queries;
SearchResultCard renders the star only when a recipe has at least one
rating.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-13 22:43:58 +02:00
parent 95a4dccce8
commit c21157fc93
9 changed files with 70 additions and 7 deletions
@@ -3,7 +3,7 @@
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Badge } from "@/components/ui/badge";
import { Clock, ChefHat } from "lucide-react";
import { Clock, ChefHat, Star } from "lucide-react";
import { cn } from "@/lib/utils";
const DIFFICULTY_COLORS: Record<string, string> = {
@@ -20,6 +20,8 @@ export type SearchResultRecipe = {
prepMins: number | null;
cookMins: number | null;
authorName: string | null;
avgRating?: number | null;
ratingCount?: number;
};
/**
@@ -58,6 +60,13 @@ export function SearchResultCard({ recipe, className }: { recipe: SearchResultRe
{recipe.description && (
<p className="mt-1 text-sm text-muted-foreground line-clamp-2">{recipe.description}</p>
)}
{!!recipe.ratingCount && recipe.avgRating != null && (
<div className="mt-1.5 flex items-center gap-1 text-xs text-muted-foreground">
<Star className="h-3.5 w-3.5 fill-yellow-400 text-yellow-400" />
<span className="font-medium text-foreground">{recipe.avgRating.toFixed(1)}</span>
<span>({recipe.ratingCount})</span>
</div>
)}
<div className="mt-3 flex items-center gap-3 text-xs text-muted-foreground">
{totalMins > 0 && (
<span className="flex items-center gap-1">