feat(mobile): Sparade recept-sida (sökbar favoritlista) + länk på hemma (SAVED-1)
This commit is contained in:
@@ -114,6 +114,10 @@ export default function HomeScreen() {
|
|||||||
<QuickLink label={t("profile.title")} glyph="⚙️" onPress={() => router.push("/profile")} />
|
<QuickLink label={t("profile.title")} glyph="⚙️" onPress={() => router.push("/profile")} />
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
<Card onPress={() => router.push("/saved-recipes")}>
|
||||||
|
<Body>⭐ Dina sparade recept</Body>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{budget.data && (
|
{budget.data && (
|
||||||
<Card>
|
<Card>
|
||||||
<Heading>{t("home.budget")}</Heading>
|
<Heading>{t("home.budget")}</Heading>
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export default function RootLayout() {
|
|||||||
<Stack.Screen name="meal-boxes" options={{ title: t("mealbox.title"), presentation: "modal" }} />
|
<Stack.Screen name="meal-boxes" options={{ title: t("mealbox.title"), presentation: "modal" }} />
|
||||||
<Stack.Screen name="household" options={{ title: t("home.household"), presentation: "modal" }} />
|
<Stack.Screen name="household" options={{ title: t("home.household"), presentation: "modal" }} />
|
||||||
<Stack.Screen name="memory" options={{ title: t("memory.title") }} />
|
<Stack.Screen name="memory" options={{ title: t("memory.title") }} />
|
||||||
|
<Stack.Screen name="saved-recipes" options={{ title: "Sparade recept" }} />
|
||||||
<Stack.Screen name="profile" options={{ title: t("profile.title"), presentation: "modal" }} />
|
<Stack.Screen name="profile" options={{ title: t("profile.title"), presentation: "modal" }} />
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="paywall"
|
name="paywall"
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { api } from "@/lib/api";
|
||||||
|
import { Card, EmptyState, ErrorView, Heading, Input, LoadingView, Screen, Small } from "@/components/ui";
|
||||||
|
import { spacing } from "@/lib/theme";
|
||||||
|
|
||||||
|
/** Dina sparade recept (favoriter) – sökbar lista. Backend: GET /v1/recipes/favorites/mine. */
|
||||||
|
|
||||||
|
interface SavedRecipe {
|
||||||
|
id: string;
|
||||||
|
titleSv: string;
|
||||||
|
totalTimeMinutes: number | null;
|
||||||
|
nutritionPerPortion: { kcal?: number } | null;
|
||||||
|
}
|
||||||
|
interface FavoritesResponse {
|
||||||
|
recipes: SavedRecipe[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SavedRecipesScreen() {
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const favs = useQuery<FavoritesResponse>({
|
||||||
|
queryKey: ["favorites-mine"],
|
||||||
|
queryFn: () => api<FavoritesResponse>("/v1/recipes/favorites/mine"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (favs.isLoading) return <LoadingView />;
|
||||||
|
if (favs.isError) return <ErrorView onRetry={() => void favs.refetch()} />;
|
||||||
|
|
||||||
|
const all = favs.data?.recipes ?? [];
|
||||||
|
const q = query.trim().toLowerCase();
|
||||||
|
const filtered = q ? all.filter((r) => r.titleSv.toLowerCase().includes(q)) : all;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Screen style={{ gap: spacing.md }}>
|
||||||
|
<Input
|
||||||
|
placeholder="Sök bland dina sparade recept"
|
||||||
|
autoCapitalize="none"
|
||||||
|
value={query}
|
||||||
|
onChangeText={setQuery}
|
||||||
|
/>
|
||||||
|
{all.length === 0 ? (
|
||||||
|
<EmptyState text="Du har inga sparade recept än. Tryck på ☆ Spara på ett recept så hamnar det här." />
|
||||||
|
) : filtered.length === 0 ? (
|
||||||
|
<EmptyState text="Inga sparade recept matchar din sökning." />
|
||||||
|
) : (
|
||||||
|
filtered.map((r) => (
|
||||||
|
<Card key={r.id} onPress={() => router.push(`/recipe/${r.id}`)} style={{ gap: spacing.xs }}>
|
||||||
|
<Heading>{r.titleSv}</Heading>
|
||||||
|
<Small>
|
||||||
|
{[
|
||||||
|
r.totalTimeMinutes != null ? `${r.totalTimeMinutes} min` : null,
|
||||||
|
r.nutritionPerPortion?.kcal != null
|
||||||
|
? `${Math.round(r.nutritionPerPortion.kcal)} kcal`
|
||||||
|
: null,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" · ")}
|
||||||
|
</Small>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user