fix(frontend): address all PR review concerns

- Fix 7px → 11px font-size on section headers in RecipePicker
- Extract shared slotActions.ts with UUID validation for planId/slotId/recipeId
- Load full recipe list in planner page load (was placeholder current-week slots)
- Update planner/+page.svelte to pass data.recipes as allRecipes to RecipePicker
- Update planner and recipes page.server.ts to use shared slot action helpers
- Fix planner page.server tests: add recipes mock for parallel GET load
- Update action tests to use valid UUIDs (were 'plan-1'/'r1' style strings)
- Add validation-path tests for blank/invalid input on all slot actions
- Add tests for recipes/+server.ts GET endpoint (DayPicker week navigation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 08:19:37 +02:00
parent ea7113ec53
commit e5d96cd85a
8 changed files with 396 additions and 185 deletions

View File

@@ -73,7 +73,7 @@
<!-- Empfohlen section -->
{#if suggestions.length > 0}
<div
style="font-size: 7px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted); padding: 5px 12px 3px; background: var(--color-subtle);"
style="font-size: 11px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted); padding: 5px 12px 3px; background: var(--color-subtle);"
>
Empfohlen · Beste Abwechslung
</div>
@@ -127,7 +127,7 @@
<!-- Alle Rezepte section -->
<div
style="font-size: 7px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted); padding: 5px 12px 3px; background: var(--color-subtle);"
style="font-size: 11px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted); padding: 5px 12px 3px; background: var(--color-subtle);"
>
Alle Rezepte
</div>

View File

@@ -0,0 +1,65 @@
import { apiClient } from '$lib/server/api';
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function isValidUuid(value: string | null): value is string {
return typeof value === 'string' && UUID_RE.test(value);
}
export async function addSlotAction({ fetch, request }: { fetch: typeof globalThis.fetch; request: Request }) {
const formData = await request.formData();
const planId = formData.get('planId') as string | null;
const slotDate = formData.get('slotDate') as string | null;
const recipeId = formData.get('recipeId') as string | null;
if (!isValidUuid(planId) || !isValidUuid(recipeId) || !slotDate) {
return { success: false, error: 'Ungültige Eingabe.' };
}
const api = apiClient(fetch);
const { data, error } = await api.POST('/v1/week-plans/{id}/slots', {
params: { path: { id: planId } },
body: { slotDate, recipeId }
});
if (error || !data) return { success: false };
return { success: true, slot: data };
}
export async function updateSlotAction({ fetch, request }: { fetch: typeof globalThis.fetch; request: Request }) {
const formData = await request.formData();
const planId = formData.get('planId') as string | null;
const slotId = formData.get('slotId') as string | null;
const recipeId = formData.get('recipeId') as string | null;
if (!isValidUuid(planId) || !isValidUuid(slotId) || !isValidUuid(recipeId)) {
return { success: false, error: 'Ungültige Eingabe.' };
}
const api = apiClient(fetch);
const { data, error } = await api.PATCH('/v1/week-plans/{planId}/slots/{slotId}', {
params: { path: { planId, slotId } },
body: { recipeId }
});
if (error || !data) return { success: false };
return { success: true, slot: data };
}
export async function deleteSlotAction({ fetch, request }: { fetch: typeof globalThis.fetch; request: Request }) {
const formData = await request.formData();
const planId = formData.get('planId') as string | null;
const slotId = formData.get('slotId') as string | null;
if (!isValidUuid(planId) || !isValidUuid(slotId)) {
return { success: false, error: 'Ungültige Eingabe.' };
}
const api = apiClient(fetch);
const { error } = await api.DELETE('/v1/week-plans/{planId}/slots/{slotId}', {
params: { path: { planId, slotId } }
});
if (error) return { success: false };
return { success: true };
}