feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m31s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m31s
Homepage:
- Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch
sortiert, lädt wenn Profil aktiv ist; versteckt sonst)
- Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon
zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die
Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1
unterdrückt. Section versteckt sich, wenn die Liste leer wird.
DB:
- Neue Migration 004_recipe_hidden_from_recent.sql (+Index)
- listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE)
- setRecipeHiddenFromRecent in actions.ts
API:
- GET /api/recipes/favorites?profile_id=X
- PATCH /api/recipes/[id] akzeptiert jetzt title und/oder
hidden_from_recent (Zod-Schema mit refine)
Rezept-Detail:
- Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben
H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab.
Kein location.reload() mehr.
- RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override.
- Neue Aktionsreihenfolge:
Zeile 1: Favorit | Wunschliste | Drucken
Zeile 2: Heute gekocht | Löschen
(Umbenennen ist jetzt am Titel statt in der Leiste.)
Icons (lucide-svelte, neues Dep):
- Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header,
Rezept-Detail, Wunschliste, Header-Dropdown:
🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe,
♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check,
🍳→ChefHat, X→X
- Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo,
Lucide käme da nicht sauber rein).
- SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme.
Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
This commit is contained in:
@@ -7,10 +7,18 @@ import {
|
||||
listComments,
|
||||
listCookingLog,
|
||||
listRatings,
|
||||
renameRecipe
|
||||
renameRecipe,
|
||||
setRecipeHiddenFromRecent
|
||||
} from '$lib/server/recipes/actions';
|
||||
|
||||
const RenameSchema = z.object({ title: z.string().min(1).max(200) });
|
||||
const PatchSchema = z
|
||||
.object({
|
||||
title: z.string().min(1).max(200).optional(),
|
||||
hidden_from_recent: z.boolean().optional()
|
||||
})
|
||||
.refine((v) => v.title !== undefined || v.hidden_from_recent !== undefined, {
|
||||
message: 'Need title or hidden_from_recent'
|
||||
});
|
||||
|
||||
function parseId(raw: string): number {
|
||||
const id = Number(raw);
|
||||
@@ -34,9 +42,15 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||
export const PATCH: RequestHandler = async ({ params, request }) => {
|
||||
const id = parseId(params.id!);
|
||||
const body = await request.json().catch(() => null);
|
||||
const parsed = RenameSchema.safeParse(body);
|
||||
const parsed = PatchSchema.safeParse(body);
|
||||
if (!parsed.success) error(400, { message: 'Invalid body' });
|
||||
renameRecipe(getDb(), id, parsed.data.title);
|
||||
const db = getDb();
|
||||
if (parsed.data.title !== undefined) {
|
||||
renameRecipe(db, id, parsed.data.title);
|
||||
}
|
||||
if (parsed.data.hidden_from_recent !== undefined) {
|
||||
setRecipeHiddenFromRecent(db, id, parsed.data.hidden_from_recent);
|
||||
}
|
||||
return json({ ok: true });
|
||||
};
|
||||
|
||||
|
||||
14
src/routes/api/recipes/favorites/+server.ts
Normal file
14
src/routes/api/recipes/favorites/+server.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { getDb } from '$lib/server/db';
|
||||
import { listFavoritesForProfile } from '$lib/server/recipes/search-local';
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
const raw = url.searchParams.get('profile_id');
|
||||
const profileId = raw === null ? NaN : Number(raw);
|
||||
if (!Number.isInteger(profileId) || profileId <= 0) {
|
||||
error(400, { message: 'profile_id required' });
|
||||
}
|
||||
const hits = listFavoritesForProfile(getDb(), profileId);
|
||||
return json({ hits });
|
||||
};
|
||||
Reference in New Issue
Block a user