Implement progressive enhancement for favorites button
Some checks failed
CI / update (push) Failing after 6s
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>
This commit is contained in:
@@ -1,33 +1,42 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
export let recipeId: string;
|
export let recipeId: string;
|
||||||
export let isFavorite: boolean = false;
|
export let isFavorite: boolean = false;
|
||||||
export let isLoggedIn: boolean = false;
|
export let isLoggedIn: boolean = false;
|
||||||
|
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
async function toggleFavorite() {
|
async function toggleFavorite(event: Event) {
|
||||||
if (!isLoggedIn || isLoading) return;
|
// If JavaScript is available, prevent form submission and handle client-side
|
||||||
|
if (browser) {
|
||||||
isLoading = true;
|
event.preventDefault();
|
||||||
|
|
||||||
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) {
|
if (!isLoggedIn || isLoading) return;
|
||||||
isFavorite = !isFavorite;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to toggle favorite:', error);
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
}
|
||||||
|
// If no JS, form will submit normally
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -55,12 +64,17 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
{#if isLoggedIn}
|
{#if isLoggedIn}
|
||||||
<button
|
<form method="post" action="?/toggleFavorite" style="display: inline;" use:enhance>
|
||||||
class="favorite-button"
|
<input type="hidden" name="recipeId" value={recipeId} />
|
||||||
disabled={isLoading}
|
<input type="hidden" name="isFavorite" value={isFavorite} />
|
||||||
on:click={toggleFavorite}
|
<button
|
||||||
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
type="submit"
|
||||||
>
|
class="favorite-button"
|
||||||
{isFavorite ? '❤️' : '🖤'}
|
disabled={isLoading}
|
||||||
</button>
|
on:click={toggleFavorite}
|
||||||
|
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
||||||
|
>
|
||||||
|
{isFavorite ? '❤️' : '🖤'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
{/if}
|
{/if}
|
@@ -1,6 +1,50 @@
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect, error } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const actions = {
|
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 }) => {
|
swapYeast: async ({ request, url }) => {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const yeastId = parseInt(formData.get('yeastId') as string);
|
const yeastId = parseInt(formData.get('yeastId') as string);
|
||||||
|
Reference in New Issue
Block a user