format files

This commit is contained in:
maxDorninger
2025-07-01 18:00:54 +02:00
parent a43748d7b5
commit 96cd9a4d01
4 changed files with 102 additions and 69 deletions

View File

@@ -1,10 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { env } from '$env/dynamic/public';
import type { Notification } from '$lib/types';
import { Button } from '$lib/components/ui/button/index.js';
const apiUrl = env.PUBLIC_API_URL;
interface NotificationResponse {
@@ -46,7 +43,7 @@
if (allResponse.ok) {
const allNotifications: NotificationResponse[] = await allResponse.json();
readNotifications = allNotifications.filter(n => n.read);
readNotifications = allNotifications.filter((n) => n.read);
}
} catch (error) {
console.error('Failed to fetch notifications:', error);
@@ -67,11 +64,11 @@
if (response.ok) {
// Move from unread to read
const notification = unreadNotifications.find(n => n.id === notificationId);
const notification = unreadNotifications.find((n) => n.id === notificationId);
if (notification) {
notification.read = true;
readNotifications = [notification, ...readNotifications];
unreadNotifications = unreadNotifications.filter(n => n.id !== notificationId);
unreadNotifications = unreadNotifications.filter((n) => n.id !== notificationId);
}
}
} catch (error) {
@@ -91,11 +88,11 @@
if (response.ok) {
// Move from read to unread
const notification = readNotifications.find(n => n.id === notificationId);
const notification = readNotifications.find((n) => n.id === notificationId);
if (notification) {
notification.read = false;
unreadNotifications = [notification, ...unreadNotifications];
readNotifications = readNotifications.filter(n => n.id !== notificationId);
readNotifications = readNotifications.filter((n) => n.id !== notificationId);
}
}
} catch (error) {
@@ -115,8 +112,8 @@
if (response.ok) {
// Remove from both lists
unreadNotifications = unreadNotifications.filter(n => n.id !== notificationId);
readNotifications = readNotifications.filter(n => n.id !== notificationId);
unreadNotifications = unreadNotifications.filter((n) => n.id !== notificationId);
readNotifications = readNotifications.filter((n) => n.id !== notificationId);
}
} catch (error) {
console.error('Failed to delete notification:', error);
@@ -128,7 +125,7 @@
try {
markingAllAsRead = true;
const promises = unreadNotifications.map(notification =>
const promises = unreadNotifications.map((notification) =>
fetch(`${apiUrl}/notification/${notification.id}/read`, {
method: 'PATCH',
headers: {
@@ -141,7 +138,10 @@
await Promise.all(promises);
// Move all unread to read
readNotifications = [...unreadNotifications.map(n => ({ ...n, read: true })), ...readNotifications];
readNotifications = [
...unreadNotifications.map((n) => ({ ...n, read: true })),
...readNotifications
];
unreadNotifications = [];
} catch (error) {
console.error('Failed to mark all notifications as read:', error);
@@ -172,7 +172,11 @@
if (lowerMessage.includes('downloaded') || lowerMessage.includes('successfully')) {
return '✅';
}
if (lowerMessage.includes('error') || lowerMessage.includes('failed') || lowerMessage.includes('failure')) {
if (
lowerMessage.includes('error') ||
lowerMessage.includes('failed') ||
lowerMessage.includes('failure')
) {
return '❌';
}
if (lowerMessage.includes('missing') || lowerMessage.includes('not found')) {
@@ -202,16 +206,16 @@
</svelte:head>
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Notifications</h1>
{#if unreadNotifications.length > 0}
<button
on:click={markAllAsRead}
disabled={markingAllAsRead}
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
>
{#if markingAllAsRead}
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<div class="h-4 w-4 animate-spin rounded-full border-b-2 border-white"></div>
{/if}
Mark All as Read
</button>
@@ -219,64 +223,76 @@
</div>
{#if loading}
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<div class="flex items-center justify-center py-12">
<div class="h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"></div>
</div>
{:else}
<!-- Unread Notifications -->
<div class="mb-8">
<div class="flex items-center gap-2 mb-4">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
Unread Notifications
</h2>
<div class="mb-4 flex items-center gap-2">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Unread Notifications</h2>
{#if unreadNotifications.length > 0}
<span class="bg-red-500 text-white text-xs px-2 py-1 rounded-full">
<span class="rounded-full bg-red-500 px-2 py-1 text-xs text-white">
{unreadNotifications.length}
</span>
{/if}
</div>
{#if unreadNotifications.length === 0}
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-6 text-center">
<div class="text-green-600 dark:text-green-400 text-4xl mb-2"></div>
<p class="text-green-800 dark:text-green-200 font-medium">All caught up!</p>
<p class="text-green-600 dark:text-green-400 text-sm">No unread notifications</p>
<div
class="rounded-lg border border-green-200 bg-green-50 p-6 text-center dark:border-green-800 dark:bg-green-900/20"
>
<div class="mb-2 text-4xl text-green-600 dark:text-green-400"></div>
<p class="font-medium text-green-800 dark:text-green-200">All caught up!</p>
<p class="text-sm text-green-600 dark:text-green-400">No unread notifications</p>
</div>
{:else}
<div class="space-y-3">
{#each unreadNotifications as notification (notification.id)}
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 shadow-sm">
<div
class="rounded-lg border border-blue-200 bg-blue-50 p-4 shadow-sm dark:border-blue-800 dark:bg-blue-900/20"
>
<div class="flex items-start justify-between gap-4">
<div class="flex items-start gap-3 flex-1">
<div class="flex flex-1 items-start gap-3">
<span class="text-2xl">{getNotificationIcon(notification.message)}</span>
<div class="flex-1">
<p class="text-gray-900 dark:text-white font-medium">
<p class="font-medium text-gray-900 dark:text-white">
{notification.message}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{formatTimestamp(notification.timestamp)}
</p>
</div>
</div>
<div class="flex items-center gap-2">
<button
on:click={() => markAsRead(notification.id)}
class="p-2 text-blue-600 hover:bg-blue-100 dark:hover:bg-blue-800 rounded-lg transition-colors"
<Button
onclick={() => markAsRead(notification.id)}
class="rounded-lg p-2 text-blue-600 transition-colors hover:bg-blue-100 dark:hover:bg-blue-800"
title="Mark as read"
>
<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="M5 13l4 4L19 7"></path>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
></path>
</svg>
</button>
<button
on:click={() => deleteNotification(notification.id)}
class="p-2 text-red-600 hover:bg-red-100 dark:hover:bg-red-800 rounded-lg transition-colors"
</Button>
<Button
onclick={() => deleteNotification(notification.id)}
class="rounded-lg p-2 text-red-600 transition-colors hover:bg-red-100 dark:hover:bg-red-800"
title="Delete notification"
>
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Button>
</div>
</div>
</div>
@@ -288,16 +304,17 @@
<!-- Read Notifications Toggle -->
<div class="mb-4">
<button
on:click={() => showRead = !showRead}
class="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
on:click={() => (showRead = !showRead)}
class="flex items-center gap-2 text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
>
<svg
class="w-4 h-4 transition-transform {showRead ? 'rotate-90' : ''}"
class="h-4 w-4 transition-transform {showRead ? 'rotate-90' : ''}"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"
></path>
</svg>
<span>Read Notifications ({readNotifications.length})</span>
</button>
@@ -307,44 +324,60 @@
{#if showRead}
<div>
{#if readNotifications.length === 0}
<div class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 text-center">
<div
class="rounded-lg border border-gray-200 bg-gray-50 p-6 text-center dark:border-gray-700 dark:bg-gray-800"
>
<p class="text-gray-500 dark:text-gray-400">No read notifications</p>
</div>
{:else}
<div class="space-y-3">
{#each readNotifications as notification (notification.id)}
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 shadow-sm opacity-75">
<div
class="rounded-lg border border-gray-200 bg-white p-4 opacity-75 shadow-sm dark:border-gray-700 dark:bg-gray-800"
>
<div class="flex items-start justify-between gap-4">
<div class="flex items-start gap-3 flex-1">
<span class="text-2xl opacity-50">{getNotificationIcon(notification.message)}</span>
<div class="flex flex-1 items-start gap-3">
<span class="text-2xl opacity-50"
>{getNotificationIcon(notification.message)}</span
>
<div class="flex-1">
<p class="text-gray-700 dark:text-gray-300">
{notification.message}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{formatTimestamp(notification.timestamp)}
</p>
</div>
</div>
<div class="flex items-center gap-2">
<button
on:click={() => markAsUnread(notification.id)}
class="p-2 text-blue-600 hover:bg-blue-100 dark:hover:bg-blue-800 rounded-lg transition-colors"
<Button
onclick={() => markAsUnread(notification.id)}
class="rounded-lg p-2 text-blue-600 transition-colors hover:bg-blue-100 dark:hover:bg-blue-800"
title="Mark as unread"
>
<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="M3 8l7.89 7.89a2 2 0 002.83 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 8l7.89 7.89a2 2 0 002.83 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
></path>
</svg>
</button>
<button
on:click={() => deleteNotification(notification.id)}
class="p-2 text-red-600 hover:bg-red-100 dark:hover:bg-red-800 rounded-lg transition-colors"
</Button>
<Button
onclick={() => deleteNotification(notification.id)}
class="rounded-lg p-2 text-red-600 transition-colors hover:bg-red-100 dark:hover:bg-red-800"
title="Delete notification"
>
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Button>
</div>
</div>
</div>