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:
@@ -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>
|
||||
Reference in New Issue
Block a user