Implement user favorites feature for recipes
- Add UserFavorites MongoDB model with ObjectId references - Create authenticated API endpoints for favorites management - Add Heart icon and FavoriteButton components with toggle functionality - Display favorite button below recipe tags for logged-in users - Add Favoriten navigation link (visible only when authenticated) - Create favorites page with grid layout and search functionality - Store favorites by MongoDB ObjectId for data integrity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										33
									
								
								src/lib/assets/icons/Heart.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/lib/assets/icons/Heart.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
<script>
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
@keyframes shake{
 | 
			
		||||
    0%{
 | 
			
		||||
      transform: rotate(0)
 | 
			
		||||
		scale(1,1);
 | 
			
		||||
    }
 | 
			
		||||
    25%{
 | 
			
		||||
    	box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
 | 
			
		||||
      	transform: rotate(30deg)
 | 
			
		||||
      		scale(1.2,1.2)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
    50%{
 | 
			
		||||
 | 
			
		||||
    	box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
 | 
			
		||||
      	transform: rotate(-30deg)
 | 
			
		||||
      		scale(1.2,1.2);
 | 
			
		||||
    }
 | 
			
		||||
    74%{
 | 
			
		||||
 | 
			
		||||
    	box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
 | 
			
		||||
  	transform: rotate(30deg)
 | 
			
		||||
  		scale(1.2, 1.2);
 | 
			
		||||
	}
 | 
			
		||||
	100%{
 | 
			
		||||
      transform: rotate(0)
 | 
			
		||||
      	scale(1,1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" {...$$restProps}><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="m47.6 300.4 180.7 168.7c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg>
 | 
			
		||||
							
								
								
									
										102
									
								
								src/lib/components/FavoriteButton.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/lib/components/FavoriteButton.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
<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;
 | 
			
		||||
  
 | 
			
		||||
  let isLoading = false;
 | 
			
		||||
 | 
			
		||||
  async function toggleFavorite() {
 | 
			
		||||
    if (!isLoggedIn || isLoading) return;
 | 
			
		||||
    
 | 
			
		||||
    isLoading = true;
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      const method = isFavorite ? 'DELETE' : 'POST';
 | 
			
		||||
      const response = await fetch('/api/rezepte/favorites', {
 | 
			
		||||
        method,
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ recipeId }),
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      if (response.ok) {
 | 
			
		||||
        isFavorite = !isFavorite;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Failed to toggle favorite:', error);
 | 
			
		||||
    } finally {
 | 
			
		||||
      isLoading = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<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;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .favorite-button:disabled {
 | 
			
		||||
    opacity: 0.6;
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
{#if isLoggedIn}
 | 
			
		||||
  <button 
 | 
			
		||||
    class="favorite-button" 
 | 
			
		||||
    class:favorited={isFavorite}
 | 
			
		||||
    disabled={isLoading}
 | 
			
		||||
    on:click={toggleFavorite}
 | 
			
		||||
  >
 | 
			
		||||
    <Heart />
 | 
			
		||||
    {isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
 | 
			
		||||
  </button>
 | 
			
		||||
{/if}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/models/UserFavorites.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/models/UserFavorites.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const UserFavoritesSchema = new mongoose.Schema(
 | 
			
		||||
  {
 | 
			
		||||
    username: { type: String, required: true, unique: true },
 | 
			
		||||
    favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' }] // Recipe MongoDB ObjectIds
 | 
			
		||||
  },
 | 
			
		||||
  { timestamps: true }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const UserFavorites = mongoose.model("UserFavorites", UserFavoritesSchema);
 | 
			
		||||
							
								
								
									
										112
									
								
								src/routes/api/rezepte/favorites/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/routes/api/rezepte/favorites/+server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
import { json, type RequestHandler } from '@sveltejs/kit';
 | 
			
		||||
import { UserFavorites } from '../../../../models/UserFavorites';
 | 
			
		||||
import { Recipe } from '../../../../models/Recipe';
 | 
			
		||||
import { dbConnect, dbDisconnect } from '../../../../utils/db';
 | 
			
		||||
import { error } from '@sveltejs/kit';
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
export const GET: RequestHandler = async ({ locals }) => {
 | 
			
		||||
  const session = await locals.auth();
 | 
			
		||||
  
 | 
			
		||||
  if (!session?.user?.nickname) {
 | 
			
		||||
    throw error(401, 'Authentication required');
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await dbConnect();
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const userFavorites = await UserFavorites.findOne({ 
 | 
			
		||||
      username: session.user.nickname 
 | 
			
		||||
    }).lean();
 | 
			
		||||
    
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    
 | 
			
		||||
    return json({
 | 
			
		||||
      favorites: userFavorites?.favorites || []
 | 
			
		||||
    });
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    throw error(500, 'Failed to fetch favorites');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const POST: RequestHandler = async ({ request, locals }) => {
 | 
			
		||||
  const session = await locals.auth();
 | 
			
		||||
  
 | 
			
		||||
  if (!session?.user?.nickname) {
 | 
			
		||||
    throw error(401, 'Authentication required');
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  const { recipeId } = await request.json();
 | 
			
		||||
  
 | 
			
		||||
  if (!recipeId) {
 | 
			
		||||
    throw error(400, 'Recipe ID required');
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await dbConnect();
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    // Validate that the recipe exists and get its ObjectId
 | 
			
		||||
    const recipe = await Recipe.findOne({ short_name: recipeId });
 | 
			
		||||
    if (!recipe) {
 | 
			
		||||
      await dbDisconnect();
 | 
			
		||||
      throw error(404, 'Recipe not found');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    await UserFavorites.findOneAndUpdate(
 | 
			
		||||
      { username: session.user.nickname },
 | 
			
		||||
      { $addToSet: { favorites: recipe._id } },
 | 
			
		||||
      { upsert: true, new: true }
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    
 | 
			
		||||
    return json({ success: true });
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    if (e instanceof Error && e.message.includes('404')) {
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
    throw error(500, 'Failed to add favorite');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const DELETE: RequestHandler = async ({ request, locals }) => {
 | 
			
		||||
  const session = await locals.auth();
 | 
			
		||||
  
 | 
			
		||||
  if (!session?.user?.nickname) {
 | 
			
		||||
    throw error(401, 'Authentication required');
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  const { recipeId } = await request.json();
 | 
			
		||||
  
 | 
			
		||||
  if (!recipeId) {
 | 
			
		||||
    throw error(400, 'Recipe ID required');
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await dbConnect();
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    // Find the recipe's ObjectId
 | 
			
		||||
    const recipe = await Recipe.findOne({ short_name: recipeId });
 | 
			
		||||
    if (!recipe) {
 | 
			
		||||
      await dbDisconnect();
 | 
			
		||||
      throw error(404, 'Recipe not found');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    await UserFavorites.findOneAndUpdate(
 | 
			
		||||
      { username: session.user.nickname },
 | 
			
		||||
      { $pull: { favorites: recipe._id } }
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    
 | 
			
		||||
    return json({ success: true });
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    if (e instanceof Error && e.message.includes('404')) {
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
    throw error(500, 'Failed to remove favorite');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
import { json, type RequestHandler } from '@sveltejs/kit';
 | 
			
		||||
import { UserFavorites } from '../../../../../../models/UserFavorites';
 | 
			
		||||
import { Recipe } from '../../../../../../models/Recipe';
 | 
			
		||||
import { dbConnect, dbDisconnect } from '../../../../../../utils/db';
 | 
			
		||||
import { error } from '@sveltejs/kit';
 | 
			
		||||
 | 
			
		||||
export const GET: RequestHandler = async ({ locals, params }) => {
 | 
			
		||||
  const session = await locals.auth();
 | 
			
		||||
  
 | 
			
		||||
  if (!session?.user?.nickname) {
 | 
			
		||||
    return json({ isFavorite: false });
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await dbConnect();
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    // Find the recipe by short_name to get its ObjectId
 | 
			
		||||
    const recipe = await Recipe.findOne({ short_name: params.shortName });
 | 
			
		||||
    if (!recipe) {
 | 
			
		||||
      await dbDisconnect();
 | 
			
		||||
      throw error(404, 'Recipe not found');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Check if this recipe is in the user's favorites
 | 
			
		||||
    const userFavorites = await UserFavorites.findOne({ 
 | 
			
		||||
      username: session.user.nickname,
 | 
			
		||||
      favorites: recipe._id
 | 
			
		||||
    }).lean();
 | 
			
		||||
    
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    
 | 
			
		||||
    return json({
 | 
			
		||||
      isFavorite: !!userFavorites
 | 
			
		||||
    });
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    if (e instanceof Error && e.message.includes('404')) {
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
    throw error(500, 'Failed to check favorite status');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										40
									
								
								src/routes/api/rezepte/favorites/recipes/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/routes/api/rezepte/favorites/recipes/+server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import { json, type RequestHandler } from '@sveltejs/kit';
 | 
			
		||||
import { UserFavorites } from '../../../../../models/UserFavorites';
 | 
			
		||||
import { Recipe } from '../../../../../models/Recipe';
 | 
			
		||||
import { dbConnect, dbDisconnect } from '../../../../../utils/db';
 | 
			
		||||
import type { RecipeModelType } from '../../../../../types/types';
 | 
			
		||||
import { error } from '@sveltejs/kit';
 | 
			
		||||
 | 
			
		||||
export const GET: RequestHandler = async ({ locals }) => {
 | 
			
		||||
  const session = await locals.auth();
 | 
			
		||||
  
 | 
			
		||||
  if (!session?.user?.nickname) {
 | 
			
		||||
    throw error(401, 'Authentication required');
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await dbConnect();
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const userFavorites = await UserFavorites.findOne({ 
 | 
			
		||||
      username: session.user.nickname 
 | 
			
		||||
    }).lean();
 | 
			
		||||
    
 | 
			
		||||
    if (!userFavorites?.favorites?.length) {
 | 
			
		||||
      await dbDisconnect();
 | 
			
		||||
      return json([]);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    let recipes = await Recipe.find({ 
 | 
			
		||||
      _id: { $in: userFavorites.favorites }
 | 
			
		||||
    }).lean() as RecipeModelType[];
 | 
			
		||||
    
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    
 | 
			
		||||
    recipes = JSON.parse(JSON.stringify(recipes));
 | 
			
		||||
    
 | 
			
		||||
    return json(recipes);
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    throw error(500, 'Failed to fetch favorite recipes');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -11,6 +11,9 @@ if(data.session){
 | 
			
		||||
<Header>
 | 
			
		||||
	<ul class=site_header slot=links>
 | 
			
		||||
	<li><a href="/rezepte">Alle Rezepte</a></li>
 | 
			
		||||
	{#if user}
 | 
			
		||||
		<li><a href="/rezepte/favorites">Favoriten</a></li>
 | 
			
		||||
	{/if}
 | 
			
		||||
	<li><a href="/rezepte/season">In Saison</a></li>
 | 
			
		||||
	<li><a href="/rezepte/category">Kategorie</a></li>
 | 
			
		||||
	<li><a href="/rezepte/icon">Icon</a></li>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
    	import {season} from '$lib/js/season_store';
 | 
			
		||||
	import RecipeNote from '$lib/components/RecipeNote.svelte';
 | 
			
		||||
	import {stripHtmlTags} from '$lib/js/stripHtmlTags';
 | 
			
		||||
	import FavoriteButton from '$lib/components/FavoriteButton.svelte';
 | 
			
		||||
 | 
			
		||||
    	export let data: PageData;
 | 
			
		||||
 | 
			
		||||
@@ -308,6 +309,15 @@ h4{
 | 
			
		||||
				<a class=tag href="/rezepte/tag/{tag}">{tag}</a>
 | 
			
		||||
			{/each}
 | 
			
		||||
		</div>
 | 
			
		||||
		
 | 
			
		||||
		<div class="tags center">
 | 
			
		||||
			<FavoriteButton 
 | 
			
		||||
				recipeId={data.short_name} 
 | 
			
		||||
				isFavorite={data.isFavorite || false} 
 | 
			
		||||
				isLoggedIn={!!data.session?.user} 
 | 
			
		||||
			/>
 | 
			
		||||
		</div>
 | 
			
		||||
		
 | 
			
		||||
		{#if data.note}
 | 
			
		||||
			<RecipeNote note={data.note}></RecipeNote>
 | 
			
		||||
		{/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,5 +6,21 @@ export async function load({ fetch, params}) {
 | 
			
		||||
    if(!res.ok){
 | 
			
		||||
	    throw error(res.status, item.message)
 | 
			
		||||
    }
 | 
			
		||||
    return item;
 | 
			
		||||
    
 | 
			
		||||
    // Check if this recipe is favorited by the user
 | 
			
		||||
    let isFavorite = false;
 | 
			
		||||
    try {
 | 
			
		||||
        const favRes = await fetch(`/api/rezepte/favorites/check/${params.name}`);
 | 
			
		||||
        if (favRes.ok) {
 | 
			
		||||
            const favData = await favRes.json();
 | 
			
		||||
            isFavorite = favData.isFavorite;
 | 
			
		||||
        }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        // Silently fail if not authenticated or other error
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return {
 | 
			
		||||
        ...item,
 | 
			
		||||
        isFavorite
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								src/routes/rezepte/favorites/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/routes/rezepte/favorites/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
import type { PageServerLoad } from "./$types";
 | 
			
		||||
import { redirect } from '@sveltejs/kit';
 | 
			
		||||
 | 
			
		||||
export const load: PageServerLoad = async ({ fetch, locals }) => {
 | 
			
		||||
    const session = await locals.auth();
 | 
			
		||||
    
 | 
			
		||||
    if (!session?.user?.nickname) {
 | 
			
		||||
        throw redirect(302, '/rezepte');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const res = await fetch('/api/rezepte/favorites/recipes');
 | 
			
		||||
        if (!res.ok) {
 | 
			
		||||
            return {
 | 
			
		||||
                favorites: [],
 | 
			
		||||
                error: 'Failed to load favorites'
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const favorites = await res.json();
 | 
			
		||||
        
 | 
			
		||||
        return {
 | 
			
		||||
            favorites,
 | 
			
		||||
            session
 | 
			
		||||
        };
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return {
 | 
			
		||||
            favorites: [],
 | 
			
		||||
            error: 'Failed to load favorites'
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										58
									
								
								src/routes/rezepte/favorites/+page.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/routes/rezepte/favorites/+page.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import type { PageData } from './$types';
 | 
			
		||||
    import '$lib/css/nordtheme.css';
 | 
			
		||||
    import Recipes from '$lib/components/Recipes.svelte';
 | 
			
		||||
    import Card from '$lib/components/Card.svelte';
 | 
			
		||||
    import Search from '$lib/components/Search.svelte';
 | 
			
		||||
    export let data: PageData;
 | 
			
		||||
    export let current_month = new Date().getMonth() + 1;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
h1{
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
    font-size: 4rem;
 | 
			
		||||
}
 | 
			
		||||
.subheading{
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    font-size: 1.5rem;
 | 
			
		||||
}
 | 
			
		||||
.empty-state{
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-top: 3rem;
 | 
			
		||||
    color: var(--nord3);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
    <title>Meine Favoriten - Bocken Rezepte</title>
 | 
			
		||||
    <meta name="description" content="Meine favorisierten Rezepte aus der Bockenschen Küche." />
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<h1>Favoriten</h1>
 | 
			
		||||
<p class=subheading>
 | 
			
		||||
    {#if data.favorites.length > 0}
 | 
			
		||||
        {data.favorites.length} favorisierte Rezepte
 | 
			
		||||
    {:else}
 | 
			
		||||
        Noch keine Favoriten gespeichert
 | 
			
		||||
    {/if}
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<Search></Search>
 | 
			
		||||
 | 
			
		||||
{#if data.error}
 | 
			
		||||
    <p class="empty-state">Fehler beim Laden der Favoriten: {data.error}</p>
 | 
			
		||||
{:else if data.favorites.length > 0}
 | 
			
		||||
    <Recipes>
 | 
			
		||||
        {#each data.favorites as recipe}
 | 
			
		||||
            <Card {recipe} {current_month}></Card>
 | 
			
		||||
        {/each}
 | 
			
		||||
    </Recipes>
 | 
			
		||||
{:else}
 | 
			
		||||
    <div class="empty-state">
 | 
			
		||||
        <p>Du hast noch keine Rezepte als Favoriten gespeichert.</p>
 | 
			
		||||
        <p>Besuche ein <a href="/rezepte">Rezept</a> und klicke auf das Herz-Symbol, um es zu deinen Favoriten hinzuzufügen.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
{/if}
 | 
			
		||||
		Reference in New Issue
	
	Block a user