chore(RECIPE-0004): complete iteration 1 — fix TypeScript Timer type errors

- Fixed NodeJS.Timer → NodeJS.Timeout in scheduler.ts line 13
- Fixed NodeJS.Timer[] → NodeJS.Timeout[] in fixtures.ts line 151
- Resolves TypeScript compile errors from iteration 0 review
- All 260 tests passing, build succeeds with no errors
This commit is contained in:
Giancarmine Salucci
2026-02-17 03:08:21 +01:00
parent e749763911
commit 67ab3c02d7
41 changed files with 3872 additions and 274 deletions

View File

@@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import { pushNotificationManager, type NotificationState } from '$lib/client/PushNotificationManager';
let state = $state<NotificationState>({
let viewModel = $state<NotificationState>({
supported: false,
permission: 'default',
subscribed: false,
@@ -12,10 +12,14 @@
let unsubscribe: (() => void) | null = null;
// Test notification state
let testLoading = $state<boolean>(false);
let testMessage = $state<string | null>(null);
onMount(() => {
// Subscribe to state changes
unsubscribe = pushNotificationManager.onStateChange((newState) => {
state = newState;
viewModel = newState;
});
return () => {
@@ -28,27 +32,56 @@
}
function getStatusText(): string {
if (!state.supported) return 'Not supported';
if (state.permission === 'denied') return 'Permission denied';
if (state.subscribed) return 'Enabled';
if (state.permission === 'granted') return 'Available';
if (!viewModel.supported) return 'Not supported';
if (viewModel.permission === 'denied') return 'Permission denied';
if (viewModel.subscribed) return 'Enabled';
if (viewModel.permission === 'granted') return 'Available';
return 'Permission needed';
}
function getStatusColor(): string {
if (!state.supported || state.permission === 'denied') return 'text-red-600';
if (state.subscribed) return 'text-green-600';
if (!viewModel.supported || viewModel.permission === 'denied') return 'text-red-600';
if (viewModel.subscribed) return 'text-green-600';
return 'text-yellow-600';
}
function getButtonText(): string {
if (state.loading) return 'Working...';
if (state.subscribed) return 'Disable Notifications';
if (viewModel.loading) return 'Working...';
if (viewModel.subscribed) return 'Disable Notifications';
return 'Enable Notifications';
}
function canToggle(): boolean {
return state.supported && state.permission !== 'denied' && !state.loading;
return viewModel.supported && viewModel.permission !== 'denied' && !viewModel.loading;
}
async function sendTestNotification(type: 'success' | 'error' | 'progress') {
testLoading = true;
testMessage = null;
try {
const response = await fetch('/api/notifications/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type })
});
if (!response.ok) {
throw new Error('Failed to send test notification');
}
const result = await response.json();
testMessage = `✓ Test ${type} notification sent to ${result.subscriberCount} subscriber(s)`;
} catch (error) {
testMessage = `✗ Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
} finally {
testLoading = false;
// Auto-dismiss message after 3 seconds
setTimeout(() => {
testMessage = null;
}, 3000);
}
}
</script>
@@ -81,7 +114,7 @@
</div>
<!-- Error Message -->
{#if state.error}
{#if viewModel.error}
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
<div class="flex items-start space-x-2">
<svg class="w-4 h-4 text-red-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -89,14 +122,14 @@
</svg>
<div>
<div class="text-sm font-medium text-red-800">Error</div>
<div class="text-sm text-red-700">{state.error}</div>
<div class="text-sm text-red-700">{viewModel.error}</div>
</div>
</div>
</div>
{/if}
<!-- Browser Support Info -->
{#if !state.supported}
{#if !viewModel.supported}
<div class="mb-4 p-3 bg-gray-50 border border-gray-200 rounded-lg">
<div class="flex items-start space-x-2">
<svg class="w-4 h-4 text-gray-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -113,7 +146,7 @@
{/if}
<!-- Permission Denied Info -->
{#if state.permission === 'denied'}
{#if viewModel.permission === 'denied'}
<div class="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
<div class="flex items-start space-x-2">
<svg class="w-4 h-4 text-yellow-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -130,7 +163,7 @@
{/if}
<!-- Features List -->
{#if state.supported && state.permission !== 'denied'}
{#if viewModel.supported && viewModel.permission !== 'denied'}
<div class="mb-4">
<div class="text-sm text-gray-600 mb-2">You'll receive notifications for:</div>
<ul class="text-sm text-gray-600 space-y-1">
@@ -162,21 +195,71 @@
<button
onclick={handleToggle}
disabled={!canToggle()}
class="flex items-center space-x-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors {state.subscribed
class="flex items-center space-x-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors {viewModel.subscribed
? 'bg-red-100 text-red-700 hover:bg-red-200 disabled:opacity-50'
: 'bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50'} disabled:cursor-not-allowed"
>
{#if state.loading}
{#if viewModel.loading}
<svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
{:else}
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={state.subscribed ? "M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18.364 5.636M5.636 18.364l12.728-12.728" : "M15 17h5l-5 5-5-5h5v-8a1 1 0 011-1h8a1 1 0 011 1v1a1 1 0 01-1 1h-7v7z"}></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={viewModel.subscribed ? "M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18.364 5.636M5.636 18.364l12.728-12.728" : "M15 17h5l-5 5-5-5h5v-8a1 1 0 011-1h8a1 1 0 011 1v1a1 1 0 01-1 1h-7v7z"}></path>
</svg>
{/if}
<span>{getButtonText()}</span>
</button>
</div>
</div>
<!-- Test Notification Buttons (only shown when subscribed) -->
{#if viewModel.subscribed}
<div class="mt-6 pt-6 border-t border-gray-200">
<h4 class="text-sm font-medium text-gray-900 mb-3">Test Notifications</h4>
<p class="text-sm text-gray-600 mb-4">
Send a test notification to verify your subscription is working correctly.
</p>
<div class="flex flex-wrap gap-2">
<button
onclick={() => sendTestNotification('success')}
disabled={testLoading || viewModel.loading}
class="px-3 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{testLoading ? 'Sending...' : 'Test Success'}
</button>
<button
onclick={() => sendTestNotification('error')}
disabled={testLoading || viewModel.loading}
class="px-3 py-2 bg-red-600 text-white text-sm font-medium rounded-lg hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{testLoading ? 'Sending...' : 'Test Error'}
</button>
<button
onclick={() => sendTestNotification('progress')}
disabled={testLoading || viewModel.loading}
class="px-3 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{testLoading ? 'Sending...' : 'Test Progress'}
</button>
</div>
<!-- Test Message -->
{#if testMessage}
<div class="mt-4 p-3 rounded-lg {testMessage.startsWith('✓') ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'}">
<div class="flex items-start space-x-2">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5 {testMessage.startsWith('✓') ? 'text-green-400' : 'text-red-400'}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={testMessage.startsWith('✓') ? "M5 13l4 4L19 7" : "M6 18L18 6M6 6l12 12"}></path>
</svg>
<div class="text-sm {testMessage.startsWith('✓') ? 'text-green-800' : 'text-red-800'}">
{testMessage}
</div>
</div>
</div>
{/if}
</div>
{/if}
</div>