feat: complete Svelte 5 migration across entire application
All checks were successful
CI / update (push) Successful in 2m8s
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:
@@ -11,17 +11,19 @@
|
||||
import AddButton from '$lib/components/AddButton.svelte';
|
||||
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters'; export let data; // Contains session data and balance from server
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
|
||||
let { data } = $props(); // Contains session data and balance from server
|
||||
|
||||
// Use server-side data, with fallback for progressive enhancement
|
||||
let balance = data.balance || {
|
||||
let balance = $state(data.balance || {
|
||||
netBalance: 0,
|
||||
recentSplits: []
|
||||
};
|
||||
let loading = false; // Start as false since we have server data
|
||||
let error = null;
|
||||
let monthlyExpensesData = { labels: [], datasets: [] };
|
||||
let expensesLoading = false;
|
||||
});
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
let error = $state(null);
|
||||
let monthlyExpensesData = $state(data.monthlyExpensesData || { labels: [], datasets: [] });
|
||||
let expensesLoading = $state(false);
|
||||
|
||||
// Component references for refreshing
|
||||
let enhancedBalanceComponent;
|
||||
@@ -31,10 +33,15 @@
|
||||
onMount(async () => {
|
||||
// Mark that JavaScript is loaded for progressive enhancement
|
||||
document.body.classList.add('js-loaded');
|
||||
await Promise.all([
|
||||
fetchBalance(),
|
||||
fetchMonthlyExpenses()
|
||||
]);
|
||||
|
||||
// Only fetch if we don't have server-side data
|
||||
if (!balance.recentSplits || balance.recentSplits.length === 0) {
|
||||
await fetchBalance();
|
||||
}
|
||||
|
||||
if (!monthlyExpensesData.datasets || monthlyExpensesData.datasets.length === 0) {
|
||||
await fetchMonthlyExpenses();
|
||||
}
|
||||
|
||||
// Listen for dashboard refresh events from the layout
|
||||
const handleDashboardRefresh = () => {
|
||||
@@ -195,7 +202,7 @@
|
||||
<a
|
||||
href="/cospend/payments/view/{split.paymentId?._id}"
|
||||
class="settlement-flow-activity"
|
||||
on:click={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
||||
onclick={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
||||
>
|
||||
<div class="settlement-activity-content">
|
||||
<div class="settlement-user-flow">
|
||||
@@ -226,7 +233,7 @@
|
||||
<a
|
||||
href="/cospend/payments/view/{split.paymentId?._id}"
|
||||
class="activity-bubble"
|
||||
on:click={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
||||
onclick={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
||||
>
|
||||
<div class="activity-header">
|
||||
<div class="user-info">
|
||||
|
||||
@@ -7,15 +7,17 @@
|
||||
import AddButton from '$lib/components/AddButton.svelte';
|
||||
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters'; export let data;
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
// Use server-side data with progressive enhancement
|
||||
let payments = data.payments || [];
|
||||
let loading = false; // Start as false since we have server data
|
||||
let error = null;
|
||||
let currentPage = Math.floor(data.currentOffset / data.limit);
|
||||
let limit = data.limit || 20;
|
||||
let hasMore = data.hasMore || false;
|
||||
let payments = $state(data.payments || []);
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
let error = $state(null);
|
||||
let currentPage = $state(Math.floor(data.currentOffset / data.limit));
|
||||
let limit = $state(data.limit || 20);
|
||||
let hasMore = $state(data.hasMore || false);
|
||||
|
||||
// Progressive enhancement: only load if JavaScript is available
|
||||
onMount(async () => {
|
||||
@@ -86,7 +88,7 @@
|
||||
if (payment.currency === 'CHF' || !payment.originalAmount) {
|
||||
return formatCurrency(payment.amount, 'CHF', 'de-CH');
|
||||
}
|
||||
|
||||
|
||||
return `${formatCurrency(payment.originalAmount, payment.currency, 'CHF', 'de-CH')} ≈ ${formatCurrency(payment.amount, 'CHF', 'de-CH')}`;
|
||||
}
|
||||
|
||||
@@ -244,7 +246,7 @@
|
||||
|
||||
<!-- Progressive enhancement: JavaScript load more button -->
|
||||
{#if hasMore}
|
||||
<button class="btn btn-secondary js-only" on:click={loadMore} disabled={loading}
|
||||
<button class="btn btn-secondary js-only" onclick={loadMore} disabled={loading}
|
||||
style="display: none;">
|
||||
{loading ? 'Loading...' : 'Load More (JS)'}
|
||||
</button>
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
import SplitMethodSelector from '$lib/components/SplitMethodSelector.svelte';
|
||||
import UsersList from '$lib/components/UsersList.svelte';
|
||||
import ImageUpload from '$lib/components/ImageUpload.svelte';
|
||||
|
||||
export let data;
|
||||
export let form;
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
// Initialize form data with server values if available (for error handling)
|
||||
let formData = {
|
||||
let formData = $state({
|
||||
title: form?.values?.title || '',
|
||||
description: form?.values?.description || '',
|
||||
amount: form?.values?.amount || '',
|
||||
@@ -25,49 +24,49 @@
|
||||
splitMethod: form?.values?.splitMethod || 'equal',
|
||||
splits: [],
|
||||
isRecurring: form?.values?.isRecurring === 'true' || false
|
||||
};
|
||||
});
|
||||
|
||||
// Recurring payment settings
|
||||
let recurringData = {
|
||||
let recurringData = $state({
|
||||
frequency: form?.values?.recurringFrequency || 'monthly',
|
||||
cronExpression: form?.values?.recurringCronExpression || '',
|
||||
startDate: form?.values?.recurringStartDate || new Date().toISOString().split('T')[0],
|
||||
endDate: form?.values?.recurringEndDate || ''
|
||||
};
|
||||
});
|
||||
|
||||
let imageFile = $state(null);
|
||||
let imagePreview = $state('');
|
||||
let uploading = $state(false);
|
||||
let newUser = $state('');
|
||||
let splitAmounts = $state({});
|
||||
let personalAmounts = $state({});
|
||||
let loading = $state(false);
|
||||
let error = $state(form?.error || null);
|
||||
let predefinedMode = $state(data.predefinedUsers.length > 0);
|
||||
let jsEnhanced = $state(false);
|
||||
let cronError = $state(false);
|
||||
let nextExecutionPreview = $state('');
|
||||
let supportedCurrencies = $state(['CHF']);
|
||||
let loadingCurrencies = $state(false);
|
||||
let currentExchangeRate = $state(null);
|
||||
let convertedAmount = $state(null);
|
||||
let loadingExchangeRate = $state(false);
|
||||
let exchangeRateError = $state(null);
|
||||
let exchangeRateTimeout = $state();
|
||||
|
||||
let imageFile = null;
|
||||
let imagePreview = '';
|
||||
let uploading = false;
|
||||
let newUser = '';
|
||||
let splitAmounts = {};
|
||||
let personalAmounts = {};
|
||||
let loading = false;
|
||||
let error = form?.error || null;
|
||||
let predefinedMode = data.predefinedUsers.length > 0;
|
||||
let jsEnhanced = false;
|
||||
let cronError = false;
|
||||
let nextExecutionPreview = '';
|
||||
let supportedCurrencies = ['CHF'];
|
||||
let loadingCurrencies = false;
|
||||
let currentExchangeRate = null;
|
||||
let convertedAmount = null;
|
||||
let loadingExchangeRate = false;
|
||||
let exchangeRateError = null;
|
||||
let exchangeRateTimeout;
|
||||
|
||||
// Initialize users from server data for no-JS support
|
||||
let users = predefinedMode ? [...data.predefinedUsers] : (data.currentUser ? [data.currentUser] : []);
|
||||
let users = $state(predefinedMode ? [...data.predefinedUsers] : (data.currentUser ? [data.currentUser] : []));
|
||||
|
||||
// Initialize split amounts for server-side users
|
||||
users.forEach(user => {
|
||||
splitAmounts[user] = 0;
|
||||
personalAmounts[user] = 0;
|
||||
});
|
||||
|
||||
$: categoryOptions = getCategoryOptions();
|
||||
|
||||
|
||||
let categoryOptions = $derived(getCategoryOptions());
|
||||
|
||||
// Reactive text for "Paid in Full" option
|
||||
$: paidInFullText = (() => {
|
||||
let paidInFullText = $derived.by(() => {
|
||||
// No-JS fallback text - always generic
|
||||
if (!jsEnhanced) {
|
||||
if (predefinedMode) {
|
||||
@@ -76,26 +75,26 @@
|
||||
return 'Paid in Full for others';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// JavaScript-enhanced reactive text
|
||||
if (!formData.paidBy) {
|
||||
return 'Paid in Full';
|
||||
}
|
||||
|
||||
|
||||
// Special handling for 2-user predefined setup
|
||||
if (predefinedMode && users.length === 2) {
|
||||
const otherUser = users.find(user => user !== formData.paidBy);
|
||||
// Always show "for" the other user (who benefits) regardless of who pays
|
||||
return otherUser ? `Paid in Full for ${otherUser}` : 'Paid in Full';
|
||||
}
|
||||
|
||||
|
||||
// General case with JS
|
||||
if (formData.paidBy === data.currentUser) {
|
||||
return 'Paid in Full by You';
|
||||
} else {
|
||||
return `Paid in Full by ${formData.paidBy}`;
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
jsEnhanced = true;
|
||||
@@ -316,20 +315,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if (recurringData.cronExpression) {
|
||||
validateCron();
|
||||
}
|
||||
$effect(() => {
|
||||
if (recurringData.cronExpression) {
|
||||
validateCron();
|
||||
}
|
||||
});
|
||||
|
||||
$: if (recurringData.frequency || recurringData.cronExpression || recurringData.startDate || formData.isRecurring) {
|
||||
updateNextExecutionPreview();
|
||||
}
|
||||
$effect(() => {
|
||||
if (recurringData.frequency || recurringData.cronExpression || recurringData.startDate || formData.isRecurring) {
|
||||
updateNextExecutionPreview();
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch exchange rate when currency, amount, or date changes
|
||||
$: if (jsEnhanced && formData.currency && formData.currency !== 'CHF' && formData.date && formData.amount) {
|
||||
// Add a small delay to avoid excessive API calls while user is typing
|
||||
clearTimeout(exchangeRateTimeout);
|
||||
exchangeRateTimeout = setTimeout(fetchExchangeRate, 300);
|
||||
}
|
||||
$effect(() => {
|
||||
if (jsEnhanced && formData.currency && formData.currency !== 'CHF' && formData.date && formData.amount) {
|
||||
// Add a small delay to avoid excessive API calls while user is typing
|
||||
clearTimeout(exchangeRateTimeout);
|
||||
exchangeRateTimeout = setTimeout(fetchExchangeRate, 300);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -5,25 +5,25 @@
|
||||
import FormSection from '$lib/components/FormSection.svelte';
|
||||
import ImageUpload from '$lib/components/ImageUpload.svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
let payment = null;
|
||||
let loading = true;
|
||||
let saving = false;
|
||||
let uploading = false;
|
||||
let error = null;
|
||||
let imageFile = null;
|
||||
let imagePreview = '';
|
||||
let supportedCurrencies = ['CHF'];
|
||||
let loadingCurrencies = false;
|
||||
let currentExchangeRate = null;
|
||||
let convertedAmount = null;
|
||||
let loadingExchangeRate = false;
|
||||
let exchangeRateError = null;
|
||||
let payment = $state(null);
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let uploading = $state(false);
|
||||
let error = $state(null);
|
||||
let imageFile = $state(null);
|
||||
let imagePreview = $state('');
|
||||
let supportedCurrencies = $state(['CHF']);
|
||||
let loadingCurrencies = $state(false);
|
||||
let currentExchangeRate = $state(null);
|
||||
let convertedAmount = $state(null);
|
||||
let loadingExchangeRate = $state(false);
|
||||
let exchangeRateError = $state(null);
|
||||
let exchangeRateTimeout;
|
||||
let jsEnhanced = false;
|
||||
|
||||
$: categoryOptions = getCategoryOptions();
|
||||
let jsEnhanced = $state(false);
|
||||
|
||||
let categoryOptions = $derived(getCategoryOptions());
|
||||
|
||||
onMount(async () => {
|
||||
jsEnhanced = true;
|
||||
@@ -124,7 +124,7 @@
|
||||
return new Date(dateString).toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
let deleting = false;
|
||||
let deleting = $state(false);
|
||||
|
||||
async function deletePayment() {
|
||||
if (!confirm('Are you sure you want to delete this payment? This action cannot be undone.')) {
|
||||
@@ -206,10 +206,12 @@
|
||||
}
|
||||
|
||||
// Reactive statement for exchange rate fetching
|
||||
$: if (jsEnhanced && payment && payment.currency && payment.currency !== 'CHF' && payment.date && payment.originalAmount) {
|
||||
clearTimeout(exchangeRateTimeout);
|
||||
exchangeRateTimeout = setTimeout(fetchExchangeRate, 300);
|
||||
}
|
||||
$effect(() => {
|
||||
if (jsEnhanced && payment && payment.currency && payment.currency !== 'CHF' && payment.date && payment.originalAmount) {
|
||||
clearTimeout(exchangeRateTimeout);
|
||||
exchangeRateTimeout = setTimeout(fetchExchangeRate, 300);
|
||||
}
|
||||
});
|
||||
|
||||
function formatDateForInput(dateString) {
|
||||
if (!dateString) return '';
|
||||
@@ -232,7 +234,7 @@
|
||||
{:else if error}
|
||||
<div class="error">Error: {error}</div>
|
||||
{:else if payment}
|
||||
<form on:submit|preventDefault={handleSubmit} class="payment-form">
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="payment-form">
|
||||
<FormSection title="Payment Details">
|
||||
<div class="form-group">
|
||||
<label for="title">Title *</label>
|
||||
@@ -328,11 +330,11 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date">Date</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
value={formatDateForInput(payment.date)}
|
||||
on:change={(e) => payment.date = new Date(e.target.value).toISOString()}
|
||||
onchange={(e) => payment.date = new Date(e.target.value).toISOString()}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -383,16 +385,16 @@
|
||||
{/if}
|
||||
|
||||
<div class="form-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-danger"
|
||||
on:click={deletePayment}
|
||||
<button
|
||||
type="button"
|
||||
class="btn-danger"
|
||||
onclick={deletePayment}
|
||||
disabled={deleting || saving}
|
||||
>
|
||||
{deleting ? 'Deleting...' : 'Delete Payment'}
|
||||
</button>
|
||||
<div class="main-actions">
|
||||
<button type="button" class="btn-secondary" on:click={() => goto('/cospend/payments')}>
|
||||
<button type="button" class="btn-secondary" onclick={() => goto('/cospend/payments')}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn-primary" disabled={saving || deleting}>
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
import EditButton from '$lib/components/EditButton.svelte';
|
||||
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters'; export let data;
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
// Use server-side data with progressive enhancement
|
||||
let payment = data.payment || null;
|
||||
let loading = false; // Start as false since we have server data
|
||||
let error = null;
|
||||
let payment = $state(data.payment || null);
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
let error = $state(null);
|
||||
|
||||
// Progressive enhancement: refresh data if JavaScript is available
|
||||
onMount(async () => {
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
import { getFrequencyDescription, formatNextExecution } from '$lib/utils/recurring';
|
||||
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||
import AddButton from '$lib/components/AddButton.svelte';
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters'; export let data;
|
||||
let { data } = $props();
|
||||
|
||||
let recurringPayments = [];
|
||||
let loading = true;
|
||||
let error = null;
|
||||
let showActiveOnly = true;
|
||||
let recurringPayments = $state([]);
|
||||
let loading = $state(true);
|
||||
let error = $state(null);
|
||||
let showActiveOnly = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await fetchRecurringPayments();
|
||||
@@ -80,9 +80,11 @@
|
||||
return new Date(dateString).toLocaleDateString('de-CH');
|
||||
}
|
||||
|
||||
$: if (showActiveOnly !== undefined) {
|
||||
fetchRecurringPayments();
|
||||
}
|
||||
$effect(() => {
|
||||
if (showActiveOnly !== undefined) {
|
||||
fetchRecurringPayments();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -199,17 +201,17 @@
|
||||
<a href="/cospend/recurring/edit/{payment._id}" class="btn btn-secondary btn-small">
|
||||
Edit
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-small"
|
||||
class:btn-warning={payment.isActive}
|
||||
<button
|
||||
class="btn btn-small"
|
||||
class:btn-warning={payment.isActive}
|
||||
class:btn-success={!payment.isActive}
|
||||
on:click={() => toggleActiveStatus(payment._id, payment.isActive)}
|
||||
onclick={() => toggleActiveStatus(payment._id, payment.isActive)}
|
||||
>
|
||||
{payment.isActive ? 'Pause' : 'Activate'}
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
class="btn btn-danger btn-small"
|
||||
on:click={() => deleteRecurringPayment(payment._id, payment.title)}
|
||||
onclick={() => deleteRecurringPayment(payment._id, payment.title)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||
import SplitMethodSelector from '$lib/components/SplitMethodSelector.svelte';
|
||||
import UsersList from '$lib/components/UsersList.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
let formData = {
|
||||
let { data } = $props();
|
||||
|
||||
let formData = $state({
|
||||
title: '',
|
||||
description: '',
|
||||
amount: '',
|
||||
@@ -24,28 +24,28 @@
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
isActive: true
|
||||
};
|
||||
});
|
||||
|
||||
let users = [];
|
||||
let newUser = '';
|
||||
let splitAmounts = {};
|
||||
let personalAmounts = {};
|
||||
let loading = false;
|
||||
let loadingPayment = true;
|
||||
let error = null;
|
||||
let predefinedMode = isPredefinedUsersMode();
|
||||
let cronError = false;
|
||||
let nextExecutionPreview = '';
|
||||
let supportedCurrencies = ['CHF'];
|
||||
let loadingCurrencies = false;
|
||||
let currentExchangeRate = null;
|
||||
let convertedAmount = null;
|
||||
let loadingExchangeRate = false;
|
||||
let exchangeRateError = null;
|
||||
let exchangeRateTimeout;
|
||||
let jsEnhanced = false;
|
||||
|
||||
$: categoryOptions = getCategoryOptions();
|
||||
let users = $state([]);
|
||||
let newUser = $state('');
|
||||
let splitAmounts = $state({});
|
||||
let personalAmounts = $state({});
|
||||
let loading = $state(false);
|
||||
let loadingPayment = $state(true);
|
||||
let error = $state(null);
|
||||
let predefinedMode = $state(isPredefinedUsersMode());
|
||||
let cronError = $state(false);
|
||||
let nextExecutionPreview = $state('');
|
||||
let supportedCurrencies = $state(['CHF']);
|
||||
let loadingCurrencies = $state(false);
|
||||
let currentExchangeRate = $state(null);
|
||||
let convertedAmount = $state(null);
|
||||
let loadingExchangeRate = $state(false);
|
||||
let exchangeRateError = $state(null);
|
||||
let exchangeRateTimeout = $state();
|
||||
let jsEnhanced = $state(false);
|
||||
|
||||
let categoryOptions = $derived(getCategoryOptions());
|
||||
|
||||
onMount(async () => {
|
||||
jsEnhanced = true;
|
||||
@@ -198,13 +198,17 @@
|
||||
}
|
||||
|
||||
|
||||
$: if (formData.cronExpression) {
|
||||
validateCron();
|
||||
}
|
||||
$effect(() => {
|
||||
if (formData.cronExpression) {
|
||||
validateCron();
|
||||
}
|
||||
});
|
||||
|
||||
$: if (formData.frequency || formData.cronExpression || formData.startDate) {
|
||||
updateNextExecutionPreview();
|
||||
}
|
||||
$effect(() => {
|
||||
if (formData.frequency || formData.cronExpression || formData.startDate) {
|
||||
updateNextExecutionPreview();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadSupportedCurrencies() {
|
||||
try {
|
||||
@@ -260,10 +264,12 @@
|
||||
}
|
||||
|
||||
// Reactive statement for exchange rate fetching
|
||||
$: if (jsEnhanced && formData.currency && formData.currency !== 'CHF' && formData.startDate && formData.amount) {
|
||||
clearTimeout(exchangeRateTimeout);
|
||||
exchangeRateTimeout = setTimeout(fetchExchangeRate, 300);
|
||||
}
|
||||
$effect(() => {
|
||||
if (jsEnhanced && formData.currency && formData.currency !== 'CHF' && formData.startDate && formData.amount) {
|
||||
clearTimeout(exchangeRateTimeout);
|
||||
exchangeRateTimeout = setTimeout(fetchExchangeRate, 300);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -283,7 +289,7 @@
|
||||
{:else if error && !formData.title}
|
||||
<div class="error">Error: {error}</div>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={handleSubmit} class="payment-form">
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="payment-form">
|
||||
<div class="form-section">
|
||||
<h2>Payment Details</h2>
|
||||
|
||||
@@ -476,7 +482,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn-secondary" on:click={() => goto('/cospend/recurring')}>
|
||||
<button type="button" class="btn-secondary" onclick={() => goto('/cospend/recurring')}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn-primary" disabled={loading || cronError}>
|
||||
|
||||
@@ -5,21 +5,22 @@
|
||||
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
||||
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters'; export let data;
|
||||
export let form;
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
// Use server-side data with progressive enhancement
|
||||
let debtData = data.debtData || {
|
||||
let debtData = $state(data.debtData || {
|
||||
whoOwesMe: [],
|
||||
whoIOwe: [],
|
||||
totalOwedToMe: 0,
|
||||
totalIOwe: 0
|
||||
};
|
||||
let loading = false; // Start as false since we have server data
|
||||
let error = data.error || form?.error || null;
|
||||
let selectedSettlement = null;
|
||||
let settlementAmount = form?.values?.amount || '';
|
||||
let submitting = false;
|
||||
});
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
let error = $state(data.error || form?.error || null);
|
||||
let selectedSettlement = $state(null);
|
||||
let settlementAmount = $state(form?.values?.amount || '');
|
||||
let submitting = $state(false);
|
||||
let predefinedMode = isPredefinedUsersMode();
|
||||
|
||||
onMount(() => {
|
||||
|
||||
Reference in New Issue
Block a user