Compare commits

2 Commits

Author SHA1 Message Date
6a8478f8a6 Implement progressive enhancement for favorites button
Some checks failed
CI / update (push) Failing after 6s
- Add server-side form handling for favorites without JavaScript
- Create toggleFavorite server action that uses existing API endpoint
- Update FavoriteButton component with form-based fallback
- Maintain JavaScript enhancement for smoother UX when available
- Use server-side fetch to reuse centralized favorites API logic

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-04 17:05:07 +02:00
be26769efb Fix Card component z-index and favorite indicator positioning
- Reduce icon z-index from 10 to 5 to prevent overlap with header
- Adjust favorite indicator position from -0.5em to 0.1em for better spacing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-04 16:49:04 +02:00
3 changed files with 90 additions and 32 deletions

View File

@@ -71,7 +71,7 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
font-size: 1.5em;
box-shadow: 0em 0em 1em 0.1em rgba(0, 0, 0, 0.6);
transition: 100ms;
z-index: 10;
z-index: 5;
}
#image{
width: 300px;
@@ -197,8 +197,8 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
.favorite-indicator{
position: absolute;
font-size: 2rem;
top: -0.5em;
left: -0.5em;
top: 0.1em;
left: 0.1em;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.8));
}

View File

@@ -1,33 +1,42 @@
<script lang="ts">
import { browser } from '$app/environment';
import { enhance } from '$app/forms';
export let recipeId: string;
export let isFavorite: boolean = false;
export let isLoggedIn: boolean = false;
let isLoading = false;
async function toggleFavorite() {
if (!isLoggedIn || isLoading) return;
async function toggleFavorite(event: Event) {
// If JavaScript is available, prevent form submission and handle client-side
if (browser) {
event.preventDefault();
isLoading = true;
if (!isLoggedIn || isLoading) return;
try {
const method = isFavorite ? 'DELETE' : 'POST';
const response = await fetch('/api/rezepte/favorites', {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ recipeId }),
});
isLoading = true;
if (response.ok) {
isFavorite = !isFavorite;
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;
}
} catch (error) {
console.error('Failed to toggle favorite:', error);
} finally {
isLoading = false;
}
// If no JS, form will submit normally
}
</script>
@@ -55,12 +64,17 @@
</style>
{#if isLoggedIn}
<button
class="favorite-button"
disabled={isLoading}
on:click={toggleFavorite}
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
>
{isFavorite ? '❤️' : '🖤'}
</button>
<form method="post" action="?/toggleFavorite" style="display: inline;" use:enhance>
<input type="hidden" name="recipeId" value={recipeId} />
<input type="hidden" name="isFavorite" value={isFavorite} />
<button
type="submit"
class="favorite-button"
disabled={isLoading}
on:click={toggleFavorite}
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
>
{isFavorite ? '❤️' : '🖤'}
</button>
</form>
{/if}

View File

@@ -1,6 +1,50 @@
import { redirect } from '@sveltejs/kit';
import { redirect, error } from '@sveltejs/kit';
export const actions = {
toggleFavorite: async ({ request, locals, url, fetch }) => {
const session = await locals.auth();
if (!session?.user?.nickname) {
throw error(401, 'Authentication required');
}
const formData = await request.formData();
const recipeId = formData.get('recipeId') as string;
const isFavorite = formData.get('isFavorite') === 'true';
if (!recipeId) {
throw error(400, 'Recipe ID required');
}
try {
// Use the existing API endpoint
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) {
const errorData = await response.text();
console.error('API error:', response.status, errorData);
throw error(response.status, `Failed to toggle favorite: ${errorData}`);
}
// Redirect back to the same page to refresh the state
throw redirect(303, url.pathname);
} catch (e) {
// If it's a redirect, let it through
if (e && typeof e === 'object' && 'status' in e && e.status === 303) {
throw e;
}
console.error('Favorite toggle error:', e);
throw error(500, 'Failed to toggle favorite');
}
},
swapYeast: async ({ request, url }) => {
const formData = await request.formData();
const yeastId = parseInt(formData.get('yeastId') as string);