diff --git a/css/components.css b/css/components.css
index bba7377..e8f0338 100644
--- a/css/components.css
+++ b/css/components.css
@@ -477,3 +477,24 @@ app-shell main {
right: 0;
left: auto;
}
+
+/* Notification Badge */
+.notification-bell {
+ position: relative;
+}
+
+.notification-badge {
+ position: absolute;
+ top: -4px;
+ right: -4px;
+ min-width: 18px;
+ height: 18px;
+ padding: 0 5px;
+ font-size: 11px;
+ font-weight: var(--font-weight-bold);
+ line-height: 18px;
+ text-align: center;
+ color: #fff;
+ background-color: var(--color-accent);
+ border-radius: var(--radius-full);
+}
diff --git a/docs/pow-server/btcpay-webhook.php b/docs/pow-server/btcpay-webhook.php
index 339d019..a1b9f28 100644
--- a/docs/pow-server/btcpay-webhook.php
+++ b/docs/pow-server/btcpay-webhook.php
@@ -109,4 +109,32 @@ if ($directusStatus >= 400) {
exit;
}
+// Create notification for listing owner
+$listingData = json_decode($directusResponse, true);
+$userCreated = $listingData['data']['user_created'] ?? null;
+
+if ($userCreated) {
+ $notifPayload = json_encode([
+ 'user_hash' => $userCreated,
+ 'type' => 'listing_published',
+ 'reference_id' => $listingId,
+ 'read' => false,
+ ]);
+
+ $notifUrl = DIRECTUS_URL . '/items/notifications';
+ $nch = curl_init($notifUrl);
+ curl_setopt_array($nch, [
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $notifPayload,
+ CURLOPT_HTTPHEADER => [
+ 'Content-Type: application/json',
+ 'Authorization: Bearer ' . DIRECTUS_TOKEN,
+ ],
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_TIMEOUT => 5,
+ ]);
+ curl_exec($nch);
+ curl_close($nch);
+}
+
echo json_encode(['ok' => true, 'listingId' => $listingId, 'action' => 'published']);
diff --git a/js/app.js b/js/app.js
index ff011d1..5634a2b 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,6 +1,7 @@
import { i18n } from './i18n.js'
import { auth } from './services/auth.js'
import { favoritesService } from './services/favorites.js'
+import { notificationsService } from './services/notifications.js'
import { setupGlobalErrorHandler } from './components/error-boundary.js'
async function initApp() {
@@ -17,6 +18,7 @@ async function initApp() {
// Restore auth session before loading components
await auth.tryRestoreSession()
favoritesService.init()
+ notificationsService.init()
await import('./components/app-shell.js')
diff --git a/js/components/app-header.js b/js/components/app-header.js
index ba430cd..1e9d591 100644
--- a/js/components/app-header.js
+++ b/js/components/app-header.js
@@ -1,6 +1,7 @@
import { i18n, t } from '../i18n.js'
import { router } from '../router.js'
import { auth } from '../services/auth.js'
+import { notificationsService } from '../services/notifications.js'
class AppHeader extends HTMLElement {
constructor() {
@@ -24,6 +25,10 @@ class AppHeader extends HTMLElement {
this.render()
this.setupEventListeners()
})
+
+ this.notifUnsubscribe = notificationsService.subscribe(() => {
+ this.updateNotificationBadge()
+ })
}
disconnectedCallback() {
@@ -31,6 +36,7 @@ class AppHeader extends HTMLElement {
document.removeEventListener('keydown', this.handleKeydown)
window.removeEventListener('scroll', this.handleScroll)
if (this.authUnsubscribe) this.authUnsubscribe()
+ if (this.notifUnsubscribe) this.notifUnsubscribe()
}
handleScroll() {
@@ -173,6 +179,13 @@ class AppHeader extends HTMLElement {
` : ''}
${auth.isLoggedIn() ? `
+
+
+ ${notificationsService.unreadCount > 0 ? `${notificationsService.unreadCount}` : ''}
+