Add favorite indicators to recipe cards and improve favorites UI
All checks were successful
CI / update (push) Successful in 17s
All checks were successful
CI / update (push) Successful in 17s
- Add heart emoji indicators to recipe cards (top-left positioning) - Show favorites across all recipe list pages (season, category, icon, tag) - Create favorites utility functions for server-side data merging - Convert client-side load files to server-side for session access - Redesign favorite button with emoji hearts (🖤/❤️) and bottom-right positioning - Fix randomizer array mutation issue causing card display glitches - Implement consistent favorite indicators with drop shadows for visibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ import "$lib/css/nordtheme.css";
|
||||
import "$lib/css/shake.css";
|
||||
import "$lib/css/icon.css";
|
||||
export let do_margin_right = false;
|
||||
export let isFavorite = false;
|
||||
export let showFavoriteIndicator = false;
|
||||
// to manually override lazy loading for top cards
|
||||
export let loading_strat : "lazy" | "eager" | undefined;
|
||||
if(loading_strat === undefined){
|
||||
@@ -192,6 +194,14 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
||||
scale: 0.9 0.9;
|
||||
}
|
||||
|
||||
.favorite-indicator{
|
||||
position: absolute;
|
||||
font-size: 2rem;
|
||||
top: -0.5em;
|
||||
left: -0.5em;
|
||||
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.8));
|
||||
}
|
||||
|
||||
.icon:hover,
|
||||
.icon:focus-visible
|
||||
{
|
||||
@@ -224,6 +234,9 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
||||
<img class:blur={!isloaded} id=image class="backdrop_blur" src={'https://bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading={loading_strat} alt="{recipe.alt}" on:load={() => isloaded=true}/>
|
||||
</div>
|
||||
</div>
|
||||
{#if showFavoriteIndicator && isFavorite}
|
||||
<div class="favorite-indicator">❤️</div>
|
||||
{/if}
|
||||
{#if icon_override || recipe.season.includes(current_month)}
|
||||
<button class=icon on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/icon/${recipe.icon}`}}>{recipe.icon}</button>
|
||||
{/if}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import Heart from '$lib/assets/icons/Heart.svelte';
|
||||
|
||||
export let recipeId: string;
|
||||
export let isFavorite: boolean = false;
|
||||
export let isLoggedIn: boolean = false;
|
||||
@@ -36,17 +34,13 @@
|
||||
<style>
|
||||
.favorite-button {
|
||||
all: unset;
|
||||
color: var(--nord0);
|
||||
font-size: 1.1rem;
|
||||
background-color: var(--nord5);
|
||||
border-radius: 10000px;
|
||||
padding: 0.5em 1em;
|
||||
transition: 100ms;
|
||||
box-shadow: 0em 0em 0.5em 0.05em rgba(0,0,0,0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: 100ms;
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
right: 0.5em;
|
||||
}
|
||||
|
||||
.favorite-button:disabled {
|
||||
@@ -54,49 +48,19 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.favorite-button.favorited {
|
||||
background-color: var(--nord11);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.favorite-button:not(.favorited):hover,
|
||||
.favorite-button:not(.favorited):focus-visible {
|
||||
transform: scale(1.1);
|
||||
background-color: var(--nord11);
|
||||
color: white;
|
||||
box-shadow: 0.1em 0.1em 0.5em 0.1em rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.favorite-button.favorited:hover,
|
||||
.favorite-button.favorited:focus-visible {
|
||||
transform: scale(1.1);
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
box-shadow: 0.1em 0.1em 0.5em 0.1em rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.favorite-button {
|
||||
background-color: var(--nord0);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.favorite-button.favorited:hover,
|
||||
.favorite-button.favorited:focus-visible {
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
}
|
||||
.favorite-button:hover,
|
||||
.favorite-button:focus-visible {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if isLoggedIn}
|
||||
<button
|
||||
class="favorite-button"
|
||||
class:favorited={isFavorite}
|
||||
class="favorite-button"
|
||||
disabled={isLoading}
|
||||
on:click={toggleFavorite}
|
||||
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
||||
>
|
||||
<Heart />
|
||||
{isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
||||
{isFavorite ? '❤️' : '🖤'}
|
||||
</button>
|
||||
{/if}
|
@@ -12,6 +12,6 @@ export function rand_array(array){
|
||||
let time = new Date()
|
||||
const seed = Math.floor(time.getTime()/MS_PER_DAY)
|
||||
let rand = mulberry32(seed)
|
||||
array.sort((a,b) => 0.5 - rand())
|
||||
return array
|
||||
// Create a copy to avoid mutating the original array
|
||||
return [...array].sort((a,b) => 0.5 - rand())
|
||||
}
|
||||
|
48
src/lib/server/favorites.ts
Normal file
48
src/lib/server/favorites.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Utility functions for handling user favorites on the server side
|
||||
*/
|
||||
|
||||
export async function getUserFavorites(fetch: any, locals: any): Promise<string[]> {
|
||||
const session = await locals.auth();
|
||||
|
||||
if (!session?.user?.nickname) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const favRes = await fetch('/api/rezepte/favorites');
|
||||
if (favRes.ok) {
|
||||
const favData = await favRes.json();
|
||||
return favData.favorites || [];
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail if favorites can't be loaded
|
||||
console.error('Error loading user favorites:', e);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function addFavoriteStatusToRecipes(recipes: any[], userFavorites: string[]): any[] {
|
||||
return recipes.map(recipe => ({
|
||||
...recipe,
|
||||
isFavorite: userFavorites.some(favId => favId.toString() === recipe._id.toString())
|
||||
}));
|
||||
}
|
||||
|
||||
export async function loadRecipesWithFavorites(
|
||||
fetch: any,
|
||||
locals: any,
|
||||
recipeLoader: () => Promise<any>
|
||||
): Promise<{ recipes: any[], session: any }> {
|
||||
const [recipes, userFavorites, session] = await Promise.all([
|
||||
recipeLoader(),
|
||||
getUserFavorites(fetch, locals),
|
||||
locals.auth()
|
||||
]);
|
||||
|
||||
return {
|
||||
recipes: addFavoriteStatusToRecipes(recipes, userFavorites),
|
||||
session
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user