Compare commits
2 Commits
bda30eb42d
...
48b94e3aef
Author | SHA1 | Date | |
---|---|---|---|
48b94e3aef
|
|||
15a72e73ca
|
@@ -1,30 +1,28 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let recipeId: string;
|
import { favorites } from '$lib/stores/favorites';
|
||||||
export let isFavorite: boolean = false;
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
export let recipeId: string; // This will be short_name from the component usage
|
||||||
|
export let isFavorite: boolean = false; // Initial state from server
|
||||||
export let isLoggedIn: boolean = false;
|
export let isLoggedIn: boolean = false;
|
||||||
|
|
||||||
|
let currentIsFavorite = isFavorite;
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
|
// Load current favorite status when component mounts
|
||||||
|
onMount(async () => {
|
||||||
|
if (isLoggedIn) {
|
||||||
|
currentIsFavorite = await favorites.isFavoriteByShortName(recipeId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function toggleFavorite() {
|
async function toggleFavorite() {
|
||||||
if (!isLoggedIn || isLoading) return;
|
if (!isLoggedIn || isLoading) return;
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const method = isFavorite ? 'DELETE' : 'POST';
|
await favorites.toggle(recipeId);
|
||||||
const response = await fetch('/api/rezepte/favorites', {
|
currentIsFavorite = !currentIsFavorite;
|
||||||
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 {
|
} finally {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
}
|
}
|
||||||
@@ -59,8 +57,8 @@
|
|||||||
class="favorite-button"
|
class="favorite-button"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
on:click={toggleFavorite}
|
on:click={toggleFavorite}
|
||||||
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
title={currentIsFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
||||||
>
|
>
|
||||||
{isFavorite ? '❤️' : '🖤'}
|
{currentIsFavorite ? '❤️' : '🖤'}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
@@ -10,20 +10,10 @@ export async function getUserFavorites(fetch: any, locals: any): Promise<string[
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use absolute URL for internal server-side fetch to avoid nginx routing issues
|
const favRes = await fetch('/api/rezepte/favorites');
|
||||||
const baseUrl = process.env.NODE_ENV === 'production'
|
|
||||||
? 'http://localhost:3000'
|
|
||||||
: 'http://localhost:5173';
|
|
||||||
|
|
||||||
console.log(`Fetching favorites from: ${baseUrl}/api/rezepte/favorites`);
|
|
||||||
const favRes = await fetch(`${baseUrl}/api/rezepte/favorites`);
|
|
||||||
|
|
||||||
if (favRes.ok) {
|
if (favRes.ok) {
|
||||||
const favData = await favRes.json();
|
const favData = await favRes.json();
|
||||||
console.log(`Loaded ${favData.favorites?.length || 0} favorites for user ${session.user.nickname}`);
|
|
||||||
return favData.favorites || [];
|
return favData.favorites || [];
|
||||||
} else {
|
|
||||||
console.error(`Favorites fetch failed with status: ${favRes.status}`);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silently fail if favorites can't be loaded
|
// Silently fail if favorites can't be loaded
|
||||||
|
156
src/lib/stores/favorites.ts
Normal file
156
src/lib/stores/favorites.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { writable, derived } from 'svelte/store';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
export interface FavoritesState {
|
||||||
|
favorites: string[]; // Array of ObjectIds
|
||||||
|
shortNameToObjectId: Map<string, string>; // Mapping from short_name to ObjectId
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the favorites store
|
||||||
|
function createFavoritesStore() {
|
||||||
|
const { subscribe, set, update } = writable<FavoritesState>({
|
||||||
|
favorites: [],
|
||||||
|
shortNameToObjectId: new Map(),
|
||||||
|
loading: false,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
|
||||||
|
// Load user's favorites from API
|
||||||
|
async load() {
|
||||||
|
update(state => ({ ...state, loading: true, error: null }));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/rezepte/favorites', {
|
||||||
|
credentials: 'include' // Ensure cookies are sent for authentication
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
favorites: data.favorites || [],
|
||||||
|
loading: false
|
||||||
|
}));
|
||||||
|
} else if (response.status === 401) {
|
||||||
|
// User not authenticated, clear favorites
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
favorites: [],
|
||||||
|
loading: false
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to load favorites: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading favorites:', error);
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to load favorites'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Add a recipe to favorites
|
||||||
|
async add(recipeShortName: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/rezepte/favorites', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ recipeId: recipeShortName }),
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Reload favorites to get the updated list with ObjectIds
|
||||||
|
await this.load();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to add favorite: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding favorite:', error);
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to add favorite'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Remove a recipe from favorites
|
||||||
|
async remove(recipeShortName: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/rezepte/favorites', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ recipeId: recipeShortName }),
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Reload favorites to get the updated list
|
||||||
|
await this.load();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to remove favorite: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing favorite:', error);
|
||||||
|
update(state => ({
|
||||||
|
...state,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to remove favorite'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Toggle favorite status by short_name
|
||||||
|
async toggle(recipeShortName: string) {
|
||||||
|
// Check if favorited by checking the current recipe list for this short_name
|
||||||
|
const response = await fetch(`/api/rezepte/favorites/check/${recipeShortName}`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const { isFavorite } = await response.json();
|
||||||
|
if (isFavorite) {
|
||||||
|
await this.remove(recipeShortName);
|
||||||
|
} else {
|
||||||
|
await this.add(recipeShortName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check if a recipe is favorited by short_name
|
||||||
|
async isFavoriteByShortName(shortName: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/rezepte/favorites/check/${shortName}`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const { isFavorite } = await response.json();
|
||||||
|
return isFavorite;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking favorite status:', error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const favorites = createFavoritesStore();
|
||||||
|
|
||||||
|
// Helper function to add favorite status to recipes on client-side
|
||||||
|
export function addFavoriteStatusToRecipes(recipes: any[], userFavorites: string[]): any[] {
|
||||||
|
return recipes.map(recipe => ({
|
||||||
|
...recipe,
|
||||||
|
isFavorite: userFavorites.includes(recipe._id?.toString() || recipe.short_name)
|
||||||
|
}));
|
||||||
|
}
|
@@ -1,18 +1,14 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import { getUserFavorites, addFavoriteStatusToRecipes } from "$lib/server/favorites";
|
|
||||||
|
|
||||||
export async function load({ fetch, locals, parent }) {
|
export async function load({ fetch }) {
|
||||||
let current_month = new Date().getMonth() + 1
|
let current_month = new Date().getMonth() + 1
|
||||||
const res_season = await fetch(`/api/rezepte/items/in_season/` + current_month);
|
const res_season = await fetch(`/api/rezepte/items/in_season/` + current_month);
|
||||||
const res_all_brief = await fetch(`/api/rezepte/items/all_brief`);
|
const res_all_brief = await fetch(`/api/rezepte/items/all_brief`);
|
||||||
const item_season = await res_season.json();
|
const item_season = await res_season.json();
|
||||||
const item_all_brief = await res_all_brief.json();
|
const item_all_brief = await res_all_brief.json();
|
||||||
|
|
||||||
// Get user favorites (session comes from parent layout)
|
|
||||||
const userFavorites = await getUserFavorites(fetch, locals);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
season: addFavoriteStatusToRecipes(item_season, userFavorites),
|
season: item_season,
|
||||||
all_brief: addFavoriteStatusToRecipes(item_all_brief, userFavorites)
|
all_brief: item_all_brief
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,12 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { favorites, addFavoriteStatusToRecipes } from '$lib/stores/favorites';
|
||||||
|
import { page } from '$app/stores';
|
||||||
import MediaScroller from '$lib/components/MediaScroller.svelte';
|
import MediaScroller from '$lib/components/MediaScroller.svelte';
|
||||||
import AddButton from '$lib/components/AddButton.svelte';
|
import AddButton from '$lib/components/AddButton.svelte';
|
||||||
import Card from '$lib/components/Card.svelte';
|
import Card from '$lib/components/Card.svelte';
|
||||||
import Search from '$lib/components/Search.svelte';
|
import Search from '$lib/components/Search.svelte';
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
export let current_month = new Date().getMonth() + 1
|
export let current_month = new Date().getMonth() + 1
|
||||||
const categories = ["Hauptspeise", "Nudel", "Brot", "Dessert", "Suppe", "Beilage", "Salat", "Kuchen", "Frühstück", "Sauce", "Zutat", "Getränk", "Aufstrich", "Guetzli", "Snack"]
|
const categories = ["Hauptspeise", "Nudel", "Brot", "Dessert", "Suppe", "Beilage", "Salat", "Kuchen", "Frühstück", "Sauce", "Zutat", "Getränk", "Aufstrich", "Guetzli", "Snack"];
|
||||||
|
|
||||||
|
// Load favorites when component mounts and user is authenticated
|
||||||
|
onMount(() => {
|
||||||
|
if ($page.data.session?.user) {
|
||||||
|
favorites.load();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reactively add favorite status to recipes
|
||||||
|
$: seasonWithFavorites = addFavoriteStatusToRecipes(data.season, $favorites.favorites);
|
||||||
|
$: allBriefWithFavorites = addFavoriteStatusToRecipes(data.all_brief, $favorites.favorites);
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
h1{
|
h1{
|
||||||
@@ -35,15 +49,15 @@ h1{
|
|||||||
<Search></Search>
|
<Search></Search>
|
||||||
|
|
||||||
<MediaScroller title="In Saison">
|
<MediaScroller title="In Saison">
|
||||||
{#each data.season as recipe}
|
{#each seasonWithFavorites as recipe}
|
||||||
<Card {recipe} {current_month} loading_strat={"eager"} do_margin_right={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user}></Card>
|
<Card {recipe} {current_month} loading_strat={"eager"} do_margin_right={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!$page.data.session?.user}></Card>
|
||||||
{/each}
|
{/each}
|
||||||
</MediaScroller>
|
</MediaScroller>
|
||||||
|
|
||||||
{#each categories as category}
|
{#each categories as category}
|
||||||
<MediaScroller title={category}>
|
<MediaScroller title={category}>
|
||||||
{#each data.all_brief.filter(recipe => recipe.category == category) as recipe}
|
{#each allBriefWithFavorites.filter(recipe => recipe.category == category) as recipe}
|
||||||
<Card {recipe} {current_month} do_margin_right={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user}></Card>
|
<Card {recipe} {current_month} do_margin_right={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!$page.data.session?.user}></Card>
|
||||||
{/each}
|
{/each}
|
||||||
</MediaScroller>
|
</MediaScroller>
|
||||||
{/each}
|
{/each}
|
||||||
|
@@ -10,12 +10,7 @@ const config = {
|
|||||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
adapter: adapter(),
|
adapter: adapter()
|
||||||
// Temporarily disable CSRF protection for local testing
|
|
||||||
// TODO: Remove this after debugging production issue
|
|
||||||
csrf: {
|
|
||||||
checkOrigin: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user