feat: complete Svelte 5 migration across entire application
All checks were successful
CI / update (push) Successful in 2m8s

Migrated all components and routes from Svelte 4 to Svelte 5 syntax:

- Converted export let → $props() with generic type syntax
- Replaced createEventDispatcher → callback props
- Migrated $: reactive statements → $derived() and $effect()
- Updated two-way bindings with $bindable()
- Fixed TypeScript syntax: added lang="ts" to script tags
- Converted inline type annotations to generic parameter syntax

- Updated deprecated event directives to Svelte 5 syntax:
  - on:click → onclick
  - on:submit → onsubmit
  - on:change → onchange

- Converted deprecated <slot> elements → {@render children()}
- Updated slot props to Snippet types
- Fixed season/icon selector components with {#snippet} blocks

- Fixed non-reactive state by converting let → $state()
- Fixed infinite loop in EnhancedBalance by converting $effect → $derived
- Fixed Chart.js integration by converting $state proxies to plain arrays
- Updated cospend dashboard and payment pages with proper reactivity

- Migrated 20+ route files from export let data → $props()
- Fixed TypeScript type annotations in page components
- Updated reactive statements in error and cospend routes

- Removed invalid onchange attribute from Toggle component
- Fixed modal ID isolation in CreateIngredientList/CreateStepList
- Fixed dark mode button visibility in TranslationApproval
- Build now succeeds with zero deprecation warnings

All functionality tested and working. No breaking changes to user experience.
This commit is contained in:
2026-01-10 16:20:43 +01:00
parent 8eee15d901
commit 5c8605c690
72 changed files with 1011 additions and 1043 deletions

View File

@@ -5,7 +5,7 @@
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
import LazyCategory from '$lib/components/LazyCategory.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1;
// Search state

View File

@@ -16,7 +16,7 @@
import { onDestroy } from 'svelte';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
// Set store for recipe translation data so UserHeader can access it
// Use $effect instead of onMount to react to data changes during client-side navigation

View File

@@ -2,7 +2,7 @@
import type { PageData } from './$types';
import '$lib/css/nordtheme.css';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
const isEnglish = data.lang === 'en';
const pageTitle = isEnglish ? 'Administration' : 'Administration';

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { PageData } from './$types';
import "$lib/css/nordtheme.css";
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
import TagCloud from '$lib/components/TagCloud.svelte';
import TagBall from '$lib/components/TagBall.svelte';

View File

@@ -2,7 +2,7 @@
import type { PageData } from './$types';
import Recipes from '$lib/components/Recipes.svelte';
import Search from '$lib/components/Search.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1;
import Card from '$lib/components/Card.svelte'
import { rand_array } from '$lib/js/randomize';

View File

@@ -16,7 +16,7 @@
import { portions } from '$lib/js/portions_store';
import { img } from '$lib/js/img_store';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let preamble = $state(data.recipe.preamble);
let addendum = $state(data.recipe.addendum);
@@ -612,8 +612,8 @@ button.action_button{
{#if !showTranslationWorkflow}
<div class=submit_buttons>
<button class=action_button on:click={doDelete}><p>Löschen</p><Cross fill=white width=2rem height=2rem></Cross></button>
<button class=action_button on:click={prepareSubmit}><p>Weiter zur Übersetzung</p><Check fill=white width=2rem height=2rem></Check></button>
<button class=action_button onclick={doDelete}><p>Löschen</p><Cross fill=white width=2rem height=2rem></Cross></button>
<button class=action_button onclick={prepareSubmit}><p>Weiter zur Übersetzung</p><Check fill=white width=2rem height=2rem></Check></button>
</div>
{/if}

View File

@@ -4,7 +4,7 @@
import Recipes from '$lib/components/Recipes.svelte';
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1;
const isEnglish = $derived(data.lang === 'en');

View File

@@ -6,7 +6,7 @@
import SeasonLayout from '$lib/components/SeasonLayout.svelte'
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
</script>
<style>
a{

View File

@@ -5,7 +5,7 @@
import MediaScroller from '$lib/components/MediaScroller.svelte';
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
import { rand_array } from '$lib/js/randomize';
// Search state
@@ -27,9 +27,11 @@
});
</script>
<IconLayout icons={data.icons} active_icon={data.icon} routePrefix="/{data.recipeLang}" lang={data.lang} recipes={data.season} onSearchResults={handleSearchResults}>
<Recipes slot=recipes>
{#each rand_array(filteredRecipes) as recipe}
<Card {recipe} icon_override=true isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
{/each}
</Recipes>
{#snippet recipesSlot()}
<Recipes>
{#each rand_array(filteredRecipes) as recipe}
<Card {recipe} icon_override=true isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
{/each}
</Recipes>
{/snippet}
</IconLayout>

View File

@@ -3,7 +3,7 @@
import Recipes from '$lib/components/Recipes.svelte';
import Search from '$lib/components/Search.svelte';
import Card from '$lib/components/Card.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1;
const isEnglish = $derived(data.lang === 'en');

View File

@@ -6,7 +6,7 @@
import SeasonLayout from '$lib/components/SeasonLayout.svelte'
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1
import { rand_array } from '$lib/js/randomize';
@@ -35,9 +35,11 @@
</script>
<SeasonLayout active_index={current_month-1} {months} routePrefix="/{data.recipeLang}" lang={data.lang} recipes={data.season} onSearchResults={handleSearchResults}>
<Recipes slot=recipes>
{#each rand_array(filteredRecipes) as recipe}
<Card {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
{/each}
</Recipes>
{#snippet recipesSlot()}
<Recipes>
{#each rand_array(filteredRecipes) as recipe}
<Card {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
{/each}
</Recipes>
{/snippet}
</SeasonLayout>

View File

@@ -5,7 +5,7 @@
import MediaScroller from '$lib/components/MediaScroller.svelte';
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
const isEnglish = $derived(data.lang === 'en');
const months = $derived(isEnglish
@@ -33,9 +33,11 @@
});
</script>
<SeasonLayout active_index={data.month -1} {months} routePrefix="/{data.recipeLang}" lang={data.lang} recipes={data.season} onSearchResults={handleSearchResults}>
<Recipes slot=recipes>
{#each rand_array(filteredRecipes) as recipe}
<Card {recipe} icon_override=true isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
{/each}
</Recipes>
{#snippet recipesSlot()}
<Recipes>
{#each rand_array(filteredRecipes) as recipe}
<Card {recipe} icon_override=true isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
{/each}
</Recipes>
{/snippet}
</SeasonLayout>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
import "$lib/css/nordtheme.css";
import TagCloud from '$lib/components/TagCloud.svelte';
import TagBall from '$lib/components/TagBall.svelte';

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { PageData } from './$types';
import Recipes from '$lib/components/Recipes.svelte';
let { data }: { data: PageData } = $props();
let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1;
import Card from '$lib/components/Card.svelte'
import Search from '$lib/components/Search.svelte';