feat(RECIPE-0009): complete iteration 0 — deduplication, notifications, UI improvements

This commit is contained in:
Giancarmine Salucci
2026-02-18 06:00:48 +01:00
parent 40e3fb0c1b
commit dfca35bde2
12 changed files with 864 additions and 395 deletions

View File

@@ -12,10 +12,6 @@
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) => {
@@ -54,35 +50,6 @@
function canToggle(): boolean {
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>
<div class="bg-white border rounded-lg shadow-sm p-6">
@@ -212,54 +179,4 @@
</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>