Add comprehensive recurring payments system with scheduling

- Add RecurringPayment model with flexible scheduling options
- Implement node-cron based scheduler for payment processing
- Create API endpoints for CRUD operations on recurring payments
- Add recurring payments management UI with create/edit forms
- Integrate scheduler initialization in hooks.server.ts
- Enhance payments/add form with progressive enhancement
- Add recurring payments button to main dashboard
- Improve server-side rendering for better performance

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-12 12:41:18 +02:00
parent c9dd7a5dc1
commit ae4184c98d
28 changed files with 4412 additions and 94 deletions

View File

@@ -8,16 +8,20 @@
import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories';
import { isSettlementPayment, getSettlementIcon, getSettlementClasses, getSettlementReceiver } from '$lib/utils/settlements';
export let data; // Used by the layout for session data
export let data; // Contains session data and balance from server
let balance = {
// Use server-side data, with fallback for progressive enhancement
let balance = data.balance || {
netBalance: 0,
recentSplits: []
};
let loading = true;
let loading = false; // Start as false since we have server data
let error = null;
// Progressive enhancement: refresh data if JavaScript is available
onMount(async () => {
// Mark that JavaScript is loaded for progressive enhancement
document.body.classList.add('js-loaded');
await fetchBalance();
});
@@ -54,9 +58,12 @@
}
function handlePaymentClick(paymentId, event) {
event.preventDefault();
// Use pushState for true shallow routing - only updates URL without navigation
pushState(`/cospend/payments/view/${paymentId}`, { paymentId });
// Progressive enhancement: if JavaScript is available, use pushState for modal behavior
if (typeof pushState !== 'undefined') {
event.preventDefault();
pushState(`/cospend/payments/view/${paymentId}`, { paymentId });
}
// Otherwise, let the regular link navigation happen (no preventDefault)
}
function getSettlementReceiverFromSplit(split) {
@@ -91,11 +98,12 @@
<p>Track and split expenses with your friends and family</p>
</div>
<EnhancedBalance />
<EnhancedBalance initialBalance={data.balance} initialDebtData={data.debtData} />
<div class="actions">
<a href="/cospend/payments/add" class="btn btn-primary">Add Payment</a>
<a href="/cospend/payments" class="btn btn-secondary">View All Payments</a>
<a href="/cospend/recurring" class="btn btn-recurring">Recurring Payments</a>
{#if balance.netBalance !== 0}
<a href="/cospend/settle" class="btn btn-settlement">Settle Debts</a>
{/if}
@@ -274,6 +282,16 @@
background-color: #e8e8e8;
}
.btn-recurring {
background: linear-gradient(135deg, #9c27b0, #673ab7);
color: white;
border: none;
}
.btn-recurring:hover {
background: linear-gradient(135deg, #8e24aa, #5e35b1);
}
.btn-settlement {
background: linear-gradient(135deg, #28a745, #20c997);
color: white;