fix(recipe): Favoriten-Markierung persistiert beim Neuladen
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 50s

Bug: Beim Neuanzeigen einer Rezeptseite war der Favoriten-Button immer
grau — isFav wurde als local $state(false) initialisiert und die
checkFavorite()-Funktion war eine Stub-Implementation, die nichts
gemacht hat. State lebte nur innerhalb einer Session.

Fix:
- Neue Server-Funktion listFavoriteProfiles(db, recipeId): number[]
  in $lib/server/recipes/actions.ts
- +page.server.ts lädt favorite_profile_ids mit in die Page-Daten
- +page.svelte macht isFav zum $derived aus favoriteProfileIds +
  aktivem Profil. toggleFavorite mutiert die lokale Liste (Add/Remove
  der aktiven Profil-ID) — beim nächsten Load ist die Server-Liste
  wieder Source of Truth.
- Alte Stub-Funktion checkFavorite() entfernt (inkl. Aufruf in
  onMount).
This commit is contained in:
hsiegeln
2026-04-17 18:43:38 +02:00
parent cf31e79fb0
commit 657d006441
3 changed files with 24 additions and 13 deletions

View File

@@ -73,6 +73,17 @@ export function isFavorite(
);
}
export function listFavoriteProfiles(
db: Database.Database,
recipeId: number
): number[] {
return (
db
.prepare('SELECT profile_id FROM favorite WHERE recipe_id = ?')
.all(recipeId) as { profile_id: number }[]
).map((r) => r.profile_id);
}
export function logCooked(
db: Database.Database,
recipeId: number,

View File

@@ -5,6 +5,7 @@ import { getRecipeById } from '$lib/server/recipes/repository';
import {
listComments,
listCookingLog,
listFavoriteProfiles,
listRatings
} from '$lib/server/recipes/actions';
@@ -17,7 +18,8 @@ export const load: PageServerLoad = async ({ params }) => {
const ratings = listRatings(db, id);
const comments = listComments(db, id);
const cooking_log = listCookingLog(db, id);
const favorite_profile_ids = listFavoriteProfiles(db, id);
const avg_stars =
ratings.length === 0 ? null : ratings.reduce((s, r) => s + r.stars, 0) / ratings.length;
return { recipe, ratings, comments, cooking_log, avg_stars };
return { recipe, ratings, comments, cooking_log, favorite_profile_ids, avg_stars };
};

View File

@@ -13,7 +13,7 @@
let ratings = $state<typeof data.ratings>([]);
let comments = $state<CommentRow[]>([]);
let cookingLog = $state<typeof data.cooking_log>([]);
let isFav = $state(false);
let favoriteProfileIds = $state<number[]>([]);
let onWishlist = $state(false);
let newComment = $state('');
@@ -21,6 +21,7 @@
ratings = [...data.ratings];
comments = [...data.comments];
cookingLog = [...data.cooking_log];
favoriteProfileIds = [...data.favorite_profile_ids];
});
const myRating = $derived(
@@ -29,14 +30,9 @@
: null
);
async function checkFavorite() {
if (!profileStore.active) {
isFav = false;
return;
}
// Fetch favorite status via list endpoint (quick hack: GET not implemented, infer from no-op)
// Not critical for MVP — we mutate state on toggle.
}
const isFav = $derived(
profileStore.active ? favoriteProfileIds.includes(profileStore.active.id) : false
);
async function setRating(stars: number) {
if (!profileStore.active) {
@@ -64,13 +60,16 @@
});
return;
}
const profileId = profileStore.active.id;
const method = isFav ? 'DELETE' : 'PUT';
await fetch(`/api/recipes/${data.recipe.id}/favorite`, {
method,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ profile_id: profileStore.active.id })
body: JSON.stringify({ profile_id: profileId })
});
isFav = !isFav;
favoriteProfileIds = isFav
? favoriteProfileIds.filter((id) => id !== profileId)
: [...favoriteProfileIds, profileId];
}
async function logCooked() {
@@ -183,7 +182,6 @@
onMount(() => {
void requestWakeLock();
void checkFavorite();
void refreshWishlistState();
const onVisibility = () => {
if (document.visibilityState === 'visible' && !wakeLock) void requestWakeLock();