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

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:
hsiegeln
2026-04-17 18:57:17 +02:00
parent 657d006441
commit 7cac02de5a
12 changed files with 420 additions and 87 deletions

View File

@@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { goto, afterNavigate } from '$app/navigation';
import { Heart, Settings, CookingPot, Globe, Utensils } from 'lucide-svelte';
import { profileStore } from '$lib/client/profile.svelte';
import ProfileSwitcher from '$lib/components/ProfileSwitcher.svelte';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
@@ -151,7 +152,7 @@
{#if r.image_path}
<img src={`/images/${r.image_path}`} alt="" loading="lazy" />
{:else}
<div class="dd-placeholder">🥘</div>
<div class="dd-placeholder"><CookingPot size={22} /></div>
{/if}
<div class="dd-body">
<div class="dd-title">{r.title}</div>
@@ -168,7 +169,8 @@
href={`/search/web?q=${encodeURIComponent(navQuery.trim())}`}
onclick={pickHit}
>
🌐 Im Internet weitersuchen
<Globe size={16} strokeWidth={2} />
<span>Im Internet weitersuchen</span>
</a>
{:else}
<p class="dd-section">Keine lokalen Rezepte aus dem Internet:</p>
@@ -190,7 +192,7 @@
{#if w.thumbnail}
<img src={w.thumbnail} alt="" loading="lazy" />
{:else}
<div class="dd-placeholder">🍽️</div>
<div class="dd-placeholder"><Utensils size={22} /></div>
{/if}
<div class="dd-body">
<div class="dd-title">{w.title}</div>
@@ -209,8 +211,12 @@
</div>
{/if}
<div class="bar-right">
<a href="/wishlist" class="nav-link" aria-label="Wunschliste">🍽️</a>
<a href="/admin" class="nav-link" aria-label="Einstellungen">⚙️</a>
<a href="/wishlist" class="nav-link" aria-label="Wunschliste">
<Heart size={20} strokeWidth={2} />
</a>
<a href="/admin" class="nav-link" aria-label="Einstellungen">
<Settings size={20} strokeWidth={2} />
</a>
<ProfileSwitcher />
</div>
</div>
@@ -363,9 +369,11 @@
letter-spacing: 0.03em;
}
.dd-web {
display: block;
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
padding: 0.75rem 0.85rem;
text-align: center;
border-top: 1px solid #e4eae7;
text-decoration: none;
color: #2b6a3d;