feat: implement remaining TODO.md feature ideas + fix mobile headers
Implements the six previously-unscoped feature ideas plus a mobile layout fix reported via screenshot: - Mobile: Recipes/Collections/Pantry/Meal Plan/Shopping Lists headers now stack and wrap instead of clipping buttons on narrow viewports. - Recipe diff/compare view: word/list diff against any past version, next to Restore in version history. - Shared meal plans & shopping lists: new shoppingListMembers/ mealPlanMembers tables (viewer/editor roles, mirrors collectionMembers), share dialogs, membership-checked routes. - PDF cookbook export: /print/collection/[id] renders a whole collection with page breaks, using the existing print-CSS pattern instead of adding a PDF rendering dependency. - Grocery delivery handoff: shopping lists can copy-as-text (works today) or send to Instacart once INSTACART_API_KEY is configured (stub adapter — real API needs a partner agreement). - Personalized "For You" feed tab: ranks public recipes by tag/ dietary overlap with the user's favorited/highly-rated history. - PWA: added manifest.json + icons on top of the existing service worker so the app is installable; cook-mode pages were already cached for offline use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { Clock, Users, ChefHat, Flame, Heart } from "lucide-react";
|
||||
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 { useLocale } from "@/lib/i18n/provider";
|
||||
@@ -99,10 +99,36 @@ function TrendingTab() {
|
||||
);
|
||||
}
|
||||
|
||||
function ForYouTab() {
|
||||
const { locale } = useLocale();
|
||||
const t = useTranslations("feed");
|
||||
const [recipes, setRecipes] = useState<FeedRecipe[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/v1/feed/for-you")
|
||||
.then((r) => r.json() as Promise<{ data: FeedRecipe[] }>)
|
||||
.then(({ data }) => setRecipes(data))
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (loading) return <p className="text-sm text-muted-foreground">{t("loading")}</p>;
|
||||
if (recipes.length === 0) return <p className="text-sm text-muted-foreground">{t("forYouEmpty")}</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{recipes.map((recipe) => (
|
||||
<RecipeCard key={recipe.id} recipe={recipe} locale={locale} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FeedPageContent({ followedCount, feedRecipes }: Props) {
|
||||
const t = useTranslations("feed");
|
||||
const { locale } = useLocale();
|
||||
const [tab, setTab] = useState<"following" | "trending">("following");
|
||||
const [tab, setTab] = useState<"following" | "trending" | "forYou">("following");
|
||||
|
||||
return (
|
||||
<div className="space-y-6 max-w-2xl">
|
||||
@@ -131,6 +157,17 @@ export function FeedPageContent({ followedCount, feedRecipes }: Props) {
|
||||
<Flame className="h-3.5 w-3.5" />
|
||||
{t("trending")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab("forYou")}
|
||||
className={`pb-2 px-1 text-sm font-medium border-b-2 transition-colors flex items-center gap-1.5 ${
|
||||
tab === "forYou"
|
||||
? "border-primary text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
{t("forYou")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tab === "following" ? (
|
||||
@@ -147,8 +184,10 @@ export function FeedPageContent({ followedCount, feedRecipes }: Props) {
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
) : tab === "trending" ? (
|
||||
<TrendingTab />
|
||||
) : (
|
||||
<ForYouTab />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user