"use client"; import { useState, useEffect, useCallback } from "react"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { Clock, Users, ChefHat, Flame, Heart, Sparkles } from "lucide-react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { useLocale } from "@/lib/i18n/provider"; const PAGE_SIZE = 20; type FeedRecipe = { id: string; title: string; description: string | null; baseServings: number; prepMins: number | null; cookMins: number | null; difficulty: string | null; aiGenerated: boolean; createdAt: string; authorId: string; authorName: string; authorUsername: string | null; authorAvatarUrl: string | null; visibility: string; favoriteCount?: number; }; type FeedResponse = { data: FeedRecipe[]; total: number; limit: number; offset: number; }; type Props = { followedCount: number; }; function RecipeCard({ recipe, locale }: { recipe: FeedRecipe; locale: string }) { return (
{recipe.authorName.slice(0, 2).toUpperCase()}
{recipe.authorName} {new Date(recipe.createdAt).toLocaleDateString(locale, { month: "short", day: "numeric" })}
{recipe.favoriteCount !== undefined && recipe.favoriteCount > 0 && ( {recipe.favoriteCount} )} {recipe.aiGenerated && AI}

{recipe.title}

{recipe.description && (

{recipe.description}

)}
{recipe.difficulty && {recipe.difficulty}} {recipe.baseServings} {recipe.prepMins && {recipe.prepMins}m} {recipe.cookMins && {recipe.cookMins}m}
); } /** Shared paginated tab: fetches `endpoint`, supports "load more", and surfaces network * failures as a real error state (with retry) instead of silently rendering an empty list. */ function PaginatedFeedTab({ endpoint, emptyMessage, }: { endpoint: string; emptyMessage: string; }) { const { locale } = useLocale(); const t = useTranslations("feed"); const [recipes, setRecipes] = useState([]); const [total, setTotal] = useState(0); const [offset, setOffset] = useState(0); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(false); const fetchPage = useCallback( async (off: number, append: boolean) => { if (append) setLoadingMore(true); else setLoading(true); setError(false); try { const res = await fetch(`${endpoint}?limit=${PAGE_SIZE}&offset=${off}`); if (!res.ok) throw new Error("Request failed"); const json = (await res.json()) as FeedResponse; setRecipes((prev) => (append ? [...prev, ...json.data] : json.data)); setTotal(json.total); setOffset(off + json.data.length); } catch { setError(true); } finally { setLoading(false); setLoadingMore(false); } }, [endpoint] ); useEffect(() => { void fetchPage(0, false); }, [fetchPage]); if (loading) return

{t("loading")}

; if (error && recipes.length === 0) { return (

{t("loadFailed")}

); } if (recipes.length === 0) return

{emptyMessage}

; const hasMore = recipes.length < total; return (
{recipes.map((recipe) => ( ))} {error && (

{t("loadFailed")}

)} {hasMore || error ? (
) : null}
); } export function FeedPageContent({ followedCount }: Props) { const t = useTranslations("feed"); const [tab, setTab] = useState<"following" | "trending" | "forYou">("following"); return (

{t("title")}

{/* Tabs */}
{tab === "following" ? ( followedCount === 0 ? (

{t("followEmpty")}

) : ( ) ) : tab === "trending" ? ( ) : ( )}
); }