feat: favorite button on the My Recipes grid; fix mobile overflow in bulk-action bar
- Added a heart toggle to all three recipe-grid views (grid card overlay, list row, compact row), hidden while in multi-select mode. Detail page already had it; this was the other place it made sense. - recipes/page.tsx now fetches which of the current page's recipes are already favorited and passes it through. - FavoriteButton gained an optional iconClassName so the unfavorited heart reads on a photo overlay (was invisible in light mode against the dark backdrop — filled/favorited state was already fine since it's opaque red). - Fixed the floating bulk-select action bar overflowing both edges of the viewport on mobile (screenshotted: centered with no max-width, wider than the screen, clipping "Supprimer" on one side and the selected-count label on the other). Now caps to viewport width, scrolls horizontally as a fallback, and drops button text labels below sm (icon + aria-label + tooltip only), matching the icon-row convention used elsewhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,8 @@ import type { Metadata } from "next";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
import { auth } from "@/lib/auth/server";
|
||||
import { db, recipes, sql, count } from "@epicure/db";
|
||||
import { eq, desc, asc, and, ilike, or } from "@epicure/db";
|
||||
import { db, recipes, favorites, sql, count } from "@epicure/db";
|
||||
import { eq, desc, asc, and, ilike, or, inArray } from "@epicure/db";
|
||||
import { RecipesHeader } from "@/components/recipe/recipes-header";
|
||||
import { RecipesEmptyState } from "@/components/recipe/recipes-empty-state";
|
||||
import { RecipesGrid } from "@/components/recipe/recipes-grid";
|
||||
@@ -76,6 +76,16 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
const total = totalRow[0]?.count ?? 0;
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
|
||||
const recipeIds = userRecipes.map((r) => r.id);
|
||||
const favoritedRows = recipeIds.length > 0
|
||||
? await db
|
||||
.select({ recipeId: favorites.recipeId })
|
||||
.from(favorites)
|
||||
.where(and(eq(favorites.userId, session.user.id), inArray(favorites.recipeId, recipeIds)))
|
||||
: [];
|
||||
const favoritedIds = new Set(favoritedRows.map((r) => r.recipeId));
|
||||
const recipesWithFavorites = userRecipes.map((r) => ({ ...r, isFavorited: favoritedIds.has(r.id) }));
|
||||
|
||||
const pageHref = (p: number) => {
|
||||
const params = new URLSearchParams();
|
||||
if (query) params.set("q", query);
|
||||
@@ -105,7 +115,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
initialTag={tagFilter ?? ""}
|
||||
/>
|
||||
<RecipesEmptyState query={query} count={total} />
|
||||
<RecipesGrid key={`${query}-${sortKey}-${visibilityFilter}-${difficultyFilter}-${tagFilter}-${page}`} recipes={userRecipes} />
|
||||
<RecipesGrid key={`${query}-${sortKey}-${visibilityFilter}-${difficultyFilter}-${tagFilter}-${page}`} recipes={recipesWithFavorites} />
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-2 pt-2">
|
||||
|
||||
Reference in New Issue
Block a user