feat: add trending/leaderboard for collections
New collection_favorites table lets users star public collections. /collections/explore mirrors the recipe explore page: trending (star count in last 7 days) and recently-added public collections. Linked from the "Discover" button on the collections page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Flame, Clock, FolderOpen, ChefHat } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { CollectionStarButton } from "@/components/collections/collection-star-button";
|
||||
import type { CollectionResult } from "@/app/(app)/collections/explore/page";
|
||||
|
||||
function CollectionCard({ collection }: { collection: CollectionResult }) {
|
||||
const t = useTranslations("collections");
|
||||
return (
|
||||
<div className="group relative rounded-lg border bg-card p-4 shadow-sm transition-shadow hover:shadow-md">
|
||||
<Link href={`/collections/${collection.id}`} className="block">
|
||||
<h3 className="font-semibold text-card-foreground group-hover:text-primary line-clamp-1 pr-10">
|
||||
{collection.name}
|
||||
</h3>
|
||||
{collection.description && (
|
||||
<p className="mt-1 text-sm text-muted-foreground line-clamp-2">{collection.description}</p>
|
||||
)}
|
||||
<div className="mt-3 flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<FolderOpen className="h-3.5 w-3.5" />
|
||||
{collection.recipeCount !== 1
|
||||
? t("recipeCountPlural", { count: collection.recipeCount })
|
||||
: t("recipeCount", { count: collection.recipeCount })}
|
||||
</span>
|
||||
{collection.authorName && (
|
||||
<span className="flex items-center gap-1">
|
||||
<ChefHat className="h-3.5 w-3.5" />
|
||||
{collection.authorName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="absolute top-3 right-3">
|
||||
<CollectionStarButton collectionId={collection.id} initialStarred={collection.starred} starCount={collection.starCount} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HorizontalScroll({ children }: { children: React.ReactNode }) {
|
||||
return <div className="flex gap-4 overflow-x-auto pb-2 -mx-4 px-4 snap-x snap-mandatory">{children}</div>;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
trending: CollectionResult[];
|
||||
recent: CollectionResult[];
|
||||
};
|
||||
|
||||
export function ExploreCollectionsContent({ trending, recent }: Props) {
|
||||
const t = useTranslations("collections");
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto space-y-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">{t("discoverTitle")}</h1>
|
||||
<p className="text-muted-foreground mt-1">{t("discoverSubtitle")}</p>
|
||||
</div>
|
||||
<Link href="/collections" className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4">
|
||||
{t("myCollectionsLink")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Flame className="h-5 w-5 text-orange-500" />
|
||||
<h2 className="text-xl font-semibold">{t("trendingThisWeek")}</h2>
|
||||
</div>
|
||||
{trending.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm py-8 text-center">{t("noTrending")}</p>
|
||||
) : (
|
||||
<HorizontalScroll>
|
||||
{trending.map((c) => (
|
||||
<div key={c.id} className="snap-start shrink-0 w-64">
|
||||
<CollectionCard collection={c} />
|
||||
</div>
|
||||
))}
|
||||
</HorizontalScroll>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Clock className="h-5 w-5 text-blue-500" />
|
||||
<h2 className="text-xl font-semibold">{t("recentlyAdded")}</h2>
|
||||
</div>
|
||||
{recent.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm py-8 text-center">{t("noRecent")}</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{recent.map((c) => <CollectionCard key={c.id} collection={c} />)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user