mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-06-25 23:47:42 +00:00
217 lines
5.3 KiB
Text
217 lines
5.3 KiB
Text
|
|
---
|
||
|
|
import Layout from '../layouts/Layout.astro';
|
||
|
|
import { API_BASE as apiBase } from '../lib/api';
|
||
|
|
---
|
||
|
|
|
||
|
|
<Layout title="Notifications">
|
||
|
|
<section class="notifications-page">
|
||
|
|
<div class="header-row">
|
||
|
|
<h1>Notifications</h1>
|
||
|
|
<button id="mark-all-read" class="btn-secondary">Mark All Read</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="notifications-list" class="notifications-list">
|
||
|
|
<p class="loading">Loading notifications...</p>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</Layout>
|
||
|
|
|
||
|
|
<script define:vars={{ apiBase }}>
|
||
|
|
const token = localStorage.getItem('token');
|
||
|
|
|
||
|
|
if (!token) {
|
||
|
|
window.location.href = '/login';
|
||
|
|
}
|
||
|
|
|
||
|
|
async function loadNotifications() {
|
||
|
|
const container = document.getElementById('notifications-list');
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch(`${apiBase}/api/notifications`, {
|
||
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
||
|
|
});
|
||
|
|
const notifications = await res.json();
|
||
|
|
|
||
|
|
if (notifications.length === 0) {
|
||
|
|
container.innerHTML = '<div class="empty"><p>No notifications yet</p></div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
container.innerHTML = notifications.map(n => `
|
||
|
|
<div class="notification ${n.is_read ? 'read' : 'unread'}" data-id="${n.id}">
|
||
|
|
<div class="notif-content">
|
||
|
|
<span class="notif-type type-${n.notification_type}">${n.notification_type}</span>
|
||
|
|
<h3 class="notif-title">${n.title}</h3>
|
||
|
|
${n.message ? `<p class="notif-message">${n.message}</p>` : ''}
|
||
|
|
<span class="notif-time">${new Date(n.created_at).toLocaleString()}</span>
|
||
|
|
</div>
|
||
|
|
<div class="notif-actions">
|
||
|
|
${n.link ? `<a href="${n.link}" class="btn-view">View</a>` : ''}
|
||
|
|
${!n.is_read ? `<button class="btn-mark-read" data-id="${n.id}">Mark Read</button>` : ''}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`).join('');
|
||
|
|
|
||
|
|
// Add mark read handlers
|
||
|
|
document.querySelectorAll('.btn-mark-read').forEach(btn => {
|
||
|
|
btn.addEventListener('click', async (e) => {
|
||
|
|
const id = (e.target).dataset.id;
|
||
|
|
await markRead(id);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
container.innerHTML = '<div class="error"><p>Failed to load notifications</p></div>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function markRead(id) {
|
||
|
|
try {
|
||
|
|
const res = await fetch(`${apiBase}/api/notifications/${id}/read`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
||
|
|
});
|
||
|
|
if (res.ok) {
|
||
|
|
loadNotifications();
|
||
|
|
}
|
||
|
|
} catch (e) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function markAllRead() {
|
||
|
|
try {
|
||
|
|
const res = await fetch(`${apiBase}/api/notifications/read-all`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
||
|
|
});
|
||
|
|
if (res.ok) {
|
||
|
|
loadNotifications();
|
||
|
|
}
|
||
|
|
} catch (e) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
loadNotifications();
|
||
|
|
document.getElementById('mark-all-read')?.addEventListener('click', markAllRead);
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.notifications-page {
|
||
|
|
padding: 2rem 0;
|
||
|
|
max-width: 700px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.header-row {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
font-size: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-secondary {
|
||
|
|
background: transparent;
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
color: var(--color-text);
|
||
|
|
padding: 0.5rem 1rem;
|
||
|
|
border-radius: 6px;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-secondary:hover {
|
||
|
|
border-color: var(--color-primary);
|
||
|
|
color: var(--color-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.notifications-list {
|
||
|
|
display: grid;
|
||
|
|
gap: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.notification {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: flex-start;
|
||
|
|
gap: 1rem;
|
||
|
|
padding: 1rem 1.5rem;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
border-radius: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.notification.unread {
|
||
|
|
border-left: 3px solid var(--color-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.notification.read {
|
||
|
|
opacity: 0.7;
|
||
|
|
}
|
||
|
|
|
||
|
|
.notif-type {
|
||
|
|
font-size: 0.625rem;
|
||
|
|
padding: 0.125rem 0.5rem;
|
||
|
|
border-radius: 4px;
|
||
|
|
text-transform: uppercase;
|
||
|
|
font-weight: 600;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
display: inline-block;
|
||
|
|
}
|
||
|
|
|
||
|
|
.type-comment { background: var(--color-info); color: var(--color-on-primary); }
|
||
|
|
.type-vote { background: var(--color-success); color: var(--color-on-primary); }
|
||
|
|
.type-proposal { background: var(--color-secondary); color: var(--color-on-primary); }
|
||
|
|
.type-community { background: var(--color-warning); color: var(--color-on-primary); }
|
||
|
|
|
||
|
|
.notif-title {
|
||
|
|
font-size: 1rem;
|
||
|
|
margin-bottom: 0.25rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.notif-message {
|
||
|
|
font-size: 0.875rem;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.notif-time {
|
||
|
|
font-size: 0.75rem;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.notif-actions {
|
||
|
|
display: flex;
|
||
|
|
gap: 0.5rem;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-view, .btn-mark-read {
|
||
|
|
font-size: 0.75rem;
|
||
|
|
padding: 0.375rem 0.75rem;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-view {
|
||
|
|
background: var(--color-primary);
|
||
|
|
color: var(--color-on-primary);
|
||
|
|
border: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-mark-read {
|
||
|
|
background: transparent;
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-mark-read:hover {
|
||
|
|
border-color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.loading, .empty, .error {
|
||
|
|
text-align: center;
|
||
|
|
padding: 3rem;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
</style>
|