diff --git a/server/routes/federation.js b/server/routes/federation.js
index ff80809..12c9857 100644
--- a/server/routes/federation.js
+++ b/server/routes/federation.js
@@ -83,8 +83,11 @@ router.post('/invite', authenticateToken, async (req, res) => {
}
// Build guest join URL for the remote user
+ // If the room has an access code, embed it so the recipient can join without manual entry
const baseUrl = process.env.APP_URL || `${req.protocol}://${req.get('host')}`;
- const joinUrl = `${baseUrl}/join/${room.uid}`;
+ const joinUrl = room.access_code
+ ? `${baseUrl}/join/${room.uid}?ac=${encodeURIComponent(room.access_code)}`
+ : `${baseUrl}/join/${room.uid}`;
// Build invitation payload
const inviteId = uuidv4();
diff --git a/server/routes/notifications.js b/server/routes/notifications.js
index 6bb7464..4bc7afd 100644
--- a/server/routes/notifications.js
+++ b/server/routes/notifications.js
@@ -45,4 +45,30 @@ router.post('/:id/read', authenticateToken, async (req, res) => {
}
});
+// DELETE /api/notifications/all — Delete all notifications for current user
+// NOTE: Declared before /:id to avoid routing collision
+router.delete('/all', authenticateToken, async (req, res) => {
+ try {
+ const db = getDb();
+ await db.run('DELETE FROM notifications WHERE user_id = ?', [req.user.id]);
+ res.json({ success: true });
+ } catch {
+ res.status(500).json({ error: 'Failed to delete notifications' });
+ }
+});
+
+// DELETE /api/notifications/:id — Delete a single notification
+router.delete('/:id', authenticateToken, async (req, res) => {
+ try {
+ const db = getDb();
+ await db.run(
+ 'DELETE FROM notifications WHERE id = ? AND user_id = ?',
+ [req.params.id, req.user.id],
+ );
+ res.json({ success: true });
+ } catch {
+ res.status(500).json({ error: 'Failed to delete notification' });
+ }
+});
+
export default router;
diff --git a/src/components/NotificationBell.jsx b/src/components/NotificationBell.jsx
index 440a016..6a03e93 100644
--- a/src/components/NotificationBell.jsx
+++ b/src/components/NotificationBell.jsx
@@ -1,5 +1,5 @@
import { useRef, useState, useEffect } from 'react';
-import { Bell, BellOff, CheckCheck, ExternalLink } from 'lucide-react';
+import { Bell, BellOff, CheckCheck, ExternalLink, Trash2, X } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { useNotifications } from '../contexts/NotificationContext';
import { useLanguage } from '../contexts/LanguageContext';
@@ -48,7 +48,7 @@ function notificationSubtitle(n, t, lang) {
}
export default function NotificationBell() {
- const { notifications, unreadCount, markRead, markAllRead } = useNotifications();
+ const { notifications, unreadCount, markRead, markAllRead, deleteNotification, clearAll } = useNotifications();
const { t, language } = useLanguage();
const navigate = useNavigate();
const [open, setOpen] = useState(false);
@@ -70,6 +70,11 @@ export default function NotificationBell() {
setOpen(false);
};
+ const handleDelete = async (e, id) => {
+ e.stopPropagation();
+ await deleteNotification(id);
+ };
+
const recent = notifications.slice(0, 20);
return (
@@ -102,16 +107,28 @@ export default function NotificationBell() {
)}
- {unreadCount > 0 && (
-
- )}
+
+ {unreadCount > 0 && (
+
+ )}
+ {notifications.length > 0 && (
+
+ )}
+
{/* List */}
@@ -127,7 +144,7 @@ export default function NotificationBell() {
handleNotificationClick(n)}
- className={`flex items-start gap-3 px-4 py-3 cursor-pointer transition-colors border-b border-th-border/50 last:border-0
+ className={`group flex items-start gap-3 px-4 py-3 cursor-pointer transition-colors border-b border-th-border/50 last:border-0
${n.read ? 'hover:bg-th-hover' : 'bg-th-accent/5 hover:bg-th-accent/10'}`}
>
{/* Icon */}
@@ -146,7 +163,7 @@ export default function NotificationBell() {
- {/* Unread dot + link indicator */}
+ {/* Right side: unread dot, link icon, delete button */}
{!n.read && (
@@ -154,6 +171,13 @@ export default function NotificationBell() {
{n.link && (
)}
+
))}
diff --git a/src/contexts/NotificationContext.jsx b/src/contexts/NotificationContext.jsx
index f450d50..643ed98 100644
--- a/src/contexts/NotificationContext.jsx
+++ b/src/contexts/NotificationContext.jsx
@@ -71,8 +71,29 @@ export function NotificationProvider({ children }) {
} catch { /* silent */ }
};
+ const deleteNotification = async (id) => {
+ try {
+ await api.delete(`/notifications/${id}`);
+ setNotifications(prev => {
+ const removed = prev.find(n => n.id === id);
+ if (removed && !removed.read) setUnreadCount(c => Math.max(0, c - 1));
+ return prev.filter(n => n.id !== id);
+ });
+ seenIds.current.delete(id);
+ } catch { /* silent */ }
+ };
+
+ const clearAll = async () => {
+ try {
+ await api.delete('/notifications/all');
+ setNotifications([]);
+ setUnreadCount(0);
+ seenIds.current = new Set();
+ } catch { /* silent */ }
+ };
+
return (
-
+
{children}
);
diff --git a/src/i18n/de.json b/src/i18n/de.json
index 8d21a7b..71a23af 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -370,7 +370,9 @@
},
"notifications": {
"bell": "Benachrichtigungen",
- "markAllRead": "Alle als gelesen markieren",
+ "markAllRead": "Alle gelesen",
+ "clearAll": "Alle löschen",
+ "delete": "Löschen",
"noNotifications": "Keine Benachrichtigungen",
"roomShareAdded": "Raum wurde mit dir geteilt",
"roomShareRemoved": "Raumzugriff wurde entfernt",
diff --git a/src/i18n/en.json b/src/i18n/en.json
index d963206..df6833c 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -370,7 +370,9 @@
},
"notifications": {
"bell": "Notifications",
- "markAllRead": "Mark all as read",
+ "markAllRead": "Mark all read",
+ "clearAll": "Clear all",
+ "delete": "Delete",
"noNotifications": "No notifications yet",
"roomShareAdded": "Room shared with you",
"roomShareRemoved": "Room access removed",
diff --git a/src/pages/GuestJoin.jsx b/src/pages/GuestJoin.jsx
index 70e6078..400a5c4 100644
--- a/src/pages/GuestJoin.jsx
+++ b/src/pages/GuestJoin.jsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
-import { useParams, Link } from 'react-router-dom';
+import { useParams, Link, useSearchParams } from 'react-router-dom';
import { Video, User, Lock, Shield, ArrowRight, Loader2, Users, Radio, AlertCircle, FileText } from 'lucide-react';
import BrandLogo from '../components/BrandLogo';
import api from '../services/api';
@@ -10,6 +10,7 @@ import { useBranding } from '../contexts/BrandingContext';
export default function GuestJoin() {
const { uid } = useParams();
+ const [searchParams] = useSearchParams();
const { t } = useLanguage();
const { user } = useAuth();
const { imprintUrl, privacyUrl } = useBranding();
@@ -19,7 +20,7 @@ export default function GuestJoin() {
const [error, setError] = useState(null);
const [joining, setJoining] = useState(false);
const [name, setName] = useState(user?.name || '');
- const [accessCode, setAccessCode] = useState('');
+ const [accessCode, setAccessCode] = useState(searchParams.get('ac') || '');
const [moderatorCode, setModeratorCode] = useState('');
const [status, setStatus] = useState({ running: false });
const [recordingConsent, setRecordingConsent] = useState(false);