import type { Metadata } from "next"; import Link from "next/link"; import { headers } from "next/headers"; import { auth } from "@/lib/auth/server"; import { db, favorites, recipes, recipePhotos, users, eq, and, or, ne, desc, inArray, count } from "@epicure/db"; import { FavoritesGrid } from "@/components/recipe/favorites-grid"; import { getMessages, formatMessage } from "@/lib/i18n/server"; export const metadata: Metadata = {}; const PAGE_SIZE = 24; export default async function FavoriteRecipesPage({ searchParams, }: { searchParams: Promise<{ page?: string }>; }) { const session = await auth.api.getSession({ headers: await headers() }); if (!session) return null; const m = getMessages((session.user as { locale?: string }).locale); const { page: pageParam } = await searchParams; const page = Math.max(1, parseInt(pageParam ?? "1", 10) || 1); const offset = (page - 1) * PAGE_SIZE; // A previously-favorited recipe stays visible to the person who favorited it even if // the author later makes their account/recipe private — same "existing access isn't // revoked, only new discovery is" rule as the private-accounts feature. const where = and( eq(favorites.userId, session.user.id), or(ne(recipes.visibility, "private"), eq(recipes.authorId, session.user.id)) ); const [rows, totalRow] = await Promise.all([ db .select({ id: recipes.id, title: recipes.title, difficulty: recipes.difficulty, prepMins: recipes.prepMins, cookMins: recipes.cookMins, authorId: recipes.authorId, authorName: users.name, }) .from(favorites) .innerJoin(recipes, eq(favorites.recipeId, recipes.id)) .innerJoin(users, eq(recipes.authorId, users.id)) .where(where) .orderBy(desc(favorites.createdAt)) .limit(PAGE_SIZE) .offset(offset), db .select({ total: count() }) .from(favorites) .innerJoin(recipes, eq(favorites.recipeId, recipes.id)) .where(where), ]); const recipeIds = rows.map((r) => r.id); const photos = recipeIds.length > 0 ? await db .select({ recipeId: recipePhotos.recipeId, storageKey: recipePhotos.storageKey, isCover: recipePhotos.isCover }) .from(recipePhotos) .where(inArray(recipePhotos.recipeId, recipeIds)) : []; const coverByRecipe = new Map(); for (const p of photos) { if (!coverByRecipe.has(p.recipeId) || p.isCover) coverByRecipe.set(p.recipeId, p.storageKey); } const total = totalRow[0]?.total ?? 0; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); const favoriteRecipes = rows.map((r) => ({ id: r.id, title: r.title, difficulty: r.difficulty, prepMins: r.prepMins, cookMins: r.cookMins, authorName: r.authorId === session.user.id ? null : r.authorName, coverPhotoKey: coverByRecipe.get(r.id) ?? null, })); return (
{m.recipes.tabMine} {m.recipes.tabFavorites}

{m.recipes.tabFavorites}

{formatMessage(total === 1 ? m.recipes.favoritesCount : m.recipes.favoritesCountPlural, { count: total })}

{totalPages > 1 && (
{page > 1 && ( Previous )} Page {page} of {totalPages} {page < totalPages && ( Next )}
)}
); }