From ed97587248d5c63edb9747f4309bf999688df99c Mon Sep 17 00:00:00 2001 From: Michelle Date: Fri, 27 Feb 2026 17:42:37 +0100 Subject: [PATCH] Add federated room detail page and improve address parsing in invites --- server/config/federation.js | 13 +- server/routes/federation.js | 4 +- src/App.jsx | 2 + src/components/FederatedRoomCard.jsx | 7 +- src/components/Navbar.jsx | 2 +- src/components/Sidebar.jsx | 2 +- src/i18n/de.json | 13 +- src/i18n/en.json | 13 +- src/pages/Dashboard.jsx | 2 +- src/pages/FederatedRoomDetail.jsx | 183 +++++++++++++++++++++++++++ src/pages/RoomDetail.jsx | 4 +- 11 files changed, 226 insertions(+), 19 deletions(-) create mode 100644 src/pages/FederatedRoomDetail.jsx diff --git a/server/config/federation.js b/server/config/federation.js index cca4106..d746533 100644 --- a/server/config/federation.js +++ b/server/config/federation.js @@ -149,12 +149,15 @@ export async function discoverInstance(domain) { * @returns {{ username: string, domain: string | null }} */ export function parseAddress(address) { - if (!address || !address.includes('@')) { - return { username: address, domain: null }; + if (!address) return { username: address, domain: null }; + // Accept both @user@domain (Mastodon-style) and user@domain + const normalized = address.startsWith('@') ? address.slice(1) : address; + if (!normalized.includes('@')) { + return { username: normalized, domain: null }; } - const atIndex = address.lastIndexOf('@'); + const atIndex = normalized.lastIndexOf('@'); return { - username: address.substring(0, atIndex), - domain: address.substring(atIndex + 1), + username: normalized.substring(0, atIndex), + domain: normalized.substring(atIndex + 1), }; } diff --git a/server/routes/federation.js b/server/routes/federation.js index 84bb0e5..47215da 100644 --- a/server/routes/federation.js +++ b/server/routes/federation.js @@ -77,7 +77,7 @@ router.post('/invite', authenticateToken, async (req, res) => { const inviteId = uuidv4(); const payload = { invite_id: inviteId, - from_user: `${req.user.name}@${getFederationDomain()}`, + from_user: `@${req.user.name}@${getFederationDomain()}`, to_user: to, room_name: room.name, room_uid: room.uid, @@ -195,7 +195,7 @@ router.post('/receive', async (req, res) => { // Send notification email (truly fire-and-forget – never blocks the response) if (targetUser.email) { - const appUrl = process.env.APP_URL || ''; + const appUrl = process.env.APP_URL || `${req.protocol}://${req.get('host')}`; const inboxUrl = `${appUrl}/federation/inbox`; const appName = process.env.APP_NAME || 'Redlight'; sendFederationInviteEmail( diff --git a/src/App.jsx b/src/App.jsx index 92f7d15..74a6536 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -15,6 +15,7 @@ import Settings from './pages/Settings'; import Admin from './pages/Admin'; import GuestJoin from './pages/GuestJoin'; import FederationInbox from './pages/FederationInbox'; +import FederatedRoomDetail from './pages/FederatedRoomDetail'; export default function App() { const { user, loading } = useAuth(); @@ -57,6 +58,7 @@ export default function App() { } /> } /> } /> + } /> {/* Catch all */} diff --git a/src/components/FederatedRoomCard.jsx b/src/components/FederatedRoomCard.jsx index e33c025..4669bb6 100644 --- a/src/components/FederatedRoomCard.jsx +++ b/src/components/FederatedRoomCard.jsx @@ -1,12 +1,15 @@ import { Globe, Trash2, ExternalLink, Hash, Users, Video, VideoOff } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; import { useLanguage } from '../contexts/LanguageContext'; import api from '../services/api'; import toast from 'react-hot-toast'; export default function FederatedRoomCard({ room, onRemove }) { const { t } = useLanguage(); + const navigate = useNavigate(); - const handleJoin = () => { + const handleJoin = (e) => { + e.stopPropagation(); window.open(room.join_url, '_blank'); }; @@ -25,7 +28,7 @@ export default function FederatedRoomCard({ room, onRemove }) { const recordingOn = room.allow_recording === 1 || room.allow_recording === true; return ( -
+
navigate(`/federation/rooms/${room.id}`)}>
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index e297785..01549ad 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -80,7 +80,7 @@ export default function Navbar({ onMenuClick }) {

{user?.display_name || user?.name}

-

{user?.email}

+

@{user?.name}

diff --git a/src/i18n/de.json b/src/i18n/de.json index 066b9bf..3e7fd5b 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -341,8 +341,8 @@ "inviteTitle": "Remote-Benutzer einladen", "inviteSubtitle": "Einen Benutzer von einer anderen Redlight-Instanz zu diesem Meeting einladen.", "addressLabel": "Benutzeradresse", - "addressPlaceholder": "benutzer@andere-instanz.de", - "addressHint": "Format: Benutzername@Domain der Redlight-Instanz", + "addressPlaceholder": "@benutzer@andere-instanz.com", + "addressHint": "Format: @Benutzername@Domain der Redlight-Instanz", "messageLabel": "Nachricht (optional)", "messagePlaceholder": "Hallo, ich lade dich zu unserem Meeting ein!", "send": "Einladung senden", @@ -376,6 +376,13 @@ "maxParticipants": "Max. Teilnehmer", "recordingOn": "Aufnahme aktiviert", "recordingOff": "Aufnahme deaktiviert", - "unlimited": "Unbegrenzt" + "unlimited": "Unbegrenzt", + "backToDashboard": "Zurück zum Dashboard", + "participantLimit": "Teilnehmerlimit gesetzt", + "recordingLabel": "Aufnahme", + "recordingOnHint": "Meetings in diesem Raum können aufgezeichnet werden", + "recordingOffHint": "Meetings in diesem Raum werden nicht aufgezeichnet", + "roomDetails": "Raumdetails", + "joinUrl": "Beitritts-URL" } } \ No newline at end of file diff --git a/src/i18n/en.json b/src/i18n/en.json index d347b7f..cc12b1f 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -341,8 +341,8 @@ "inviteTitle": "Invite Remote User", "inviteSubtitle": "Invite a user from another Redlight instance to this meeting.", "addressLabel": "User address", - "addressPlaceholder": "user@other-instance.com", - "addressHint": "Format: username@domain of the Redlight instance", + "addressPlaceholder": "@user@other-instance.com", + "addressHint": "Format: @username@domain of the Redlight instance", "messageLabel": "Message (optional)", "messagePlaceholder": "Hi, I'd like to invite you to our meeting!", "send": "Send invitation", @@ -376,6 +376,13 @@ "maxParticipants": "Max. participants", "recordingOn": "Recording enabled", "recordingOff": "Recording disabled", - "unlimited": "Unlimited" + "unlimited": "Unlimited", + "backToDashboard": "Back to Dashboard", + "participantLimit": "Participant limit set", + "recordingLabel": "Recording", + "recordingOnHint": "Meetings in this room may be recorded", + "recordingOffHint": "Meetings in this room will not be recorded", + "roomDetails": "Room Details", + "joinUrl": "Join URL" } } \ No newline at end of file diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 6f57c78..d5f7eab 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -130,7 +130,7 @@ export default function Dashboard() {
{/* Room grid/list */} - {rooms.length === 0 ? ( + {rooms.length === 0 && federatedRooms.length === 0 ? (