"use client"; import Image from "next/image"; import { useTranslations } from "next-intl"; import { Globe, Lock, Link2, ExternalLink, ChefHat, Clock, Users, Utensils, UserCheck } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Badge } from "@/components/ui/badge"; import { FavoriteButton } from "@/components/social/favorite-button"; import { RecipeCoverPlaceholder } from "@/components/recipe/recipe-cover-placeholder"; import { getPublicUrl } from "@/lib/storage"; import { cn, stripMarkdown } from "@/lib/utils"; export type GridCardRecipe = { id: string; title: string; description: string | null; baseServings: number; prepMins: number | null; cookMins: number | null; difficulty: "easy" | "medium" | "hard" | null; visibility: "private" | "unlisted" | "public" | "followers"; tags: string[]; photos?: Array<{ storageKey: string; isCover: boolean }>; coverIcon?: string | null; coverColor?: string | null; isFavorited?: boolean; isBatchCook?: boolean; dishCount?: number; sourceUrl?: string | null; recipeType?: "dish" | "drink"; authorName?: string | null; }; const DIFFICULTY_COLOR = { easy: "default", medium: "secondary", hard: "destructive" } as const; const VISIBILITY_ICON = { private: Lock, unlisted: Link2, public: Globe, followers: UserCheck }; function hostnameOf(url: string): string { try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return url; } } /** Shared cover-photo/emoji-fallback thumbnail used by the recipe grid card. */ export function RecipeThumb({ recipe, className }: { recipe: GridCardRecipe; className?: string }) { const cover = recipe.photos?.find((p) => p.isCover) ?? recipe.photos?.[0]; return (
{cover ? ( {recipe.title} ) : ( )}
); } /** The recipe grid card shown on Recipes, Explore, and the feed tabs — one visual identity everywhere a recipe appears in a grid. */ export function RecipeGridCard({ recipe }: { recipe: GridCardRecipe }) { const t = useTranslations("recipe"); const tBatch = useTranslations("batchCooking"); const totalMins = (recipe.prepMins ?? 0) + (recipe.cookMins ?? 0); const VisibilityIcon = VISIBILITY_ICON[recipe.visibility]; return (
{recipe.authorName && (

{recipe.authorName}

)}

{recipe.title}

{recipe.description && (

{recipe.isBatchCook ? stripMarkdown(recipe.description) : recipe.description}

)} {recipe.tags.length > 0 && (
{recipe.tags.slice(0, 3).map((tag) => ( {tag} ))} {recipe.tags.length > 3 && ( +{recipe.tags.length - 3} )}
)}
{recipe.isBatchCook && recipe.dishCount ? ( {tBatch("dishCount", { count: recipe.dishCount })} ) : ( {recipe.baseServings} )} {totalMins > 0 && {t("total", { mins: totalMins })}} {recipe.difficulty && ( {t(`difficulty.${recipe.difficulty}`)} )}
{recipe.sourceUrl && ( } /> {t("importedFrom", { host: hostnameOf(recipe.sourceUrl) })} )} {recipe.isBatchCook && ( } /> {t("batchCookBadge")} )} { e.preventDefault(); e.stopPropagation(); }}> } /> {t(`visibility.${recipe.visibility}`)}
); }