fix: give Explore its own /explore route so PiP navigates back to voice correctly

The Explore page shared /channels/@me with Friends, differentiated by a
showExplore UI flag. When PiP navigated to a voice channel, the stale
flag caused MainContent to render text chat instead of the voice grid.
Replace the flag with a dedicated /explore route so the URL is the
single source of truth.
This commit is contained in:
Jannis Braun
2026-03-05 01:29:16 +01:00
parent b840d2f82b
commit b642989e6e
7 changed files with 25 additions and 23 deletions
+8
View File
@@ -52,6 +52,14 @@ export function App() {
</ProtectedRoute> </ProtectedRoute>
} }
/> />
<Route
path="/explore"
element={
<ProtectedRoute>
<AppLayout />
</ProtectedRoute>
}
/>
<Route path="/" element={<Navigate to="/channels/@me" replace />} /> <Route path="/" element={<Navigate to="/channels/@me" replace />} />
<Route path="*" element={<Navigate to="/channels/@me" replace />} /> <Route path="*" element={<Navigate to="/channels/@me" replace />} />
</Routes> </Routes>
@@ -2,14 +2,12 @@ import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useExploreStore, type TaggedExploreServer } from '../../stores/exploreStore'; import { useExploreStore, type TaggedExploreServer } from '../../stores/exploreStore';
import { useServerStore } from '../../stores/serverStore'; import { useServerStore } from '../../stores/serverStore';
import { useUIStore } from '../../stores/uiStore';
import { LoadingSpinner } from '../ui/LoadingSpinner'; import { LoadingSpinner } from '../ui/LoadingSpinner';
import { getServerGradient } from '../../utils/gradients'; import { getServerGradient } from '../../utils/gradients';
export function ExplorePage() { export function ExplorePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const setCurrentServer = useServerStore((s) => s.setCurrentServer); const setCurrentServer = useServerStore((s) => s.setCurrentServer);
const setShowExplore = useUIStore((s) => s.setShowExplore);
const servers = useExploreStore((s) => s.servers); const servers = useExploreStore((s) => s.servers);
const myRequests = useExploreStore((s) => s.myRequests); const myRequests = useExploreStore((s) => s.myRequests);
@@ -47,7 +45,6 @@ export function ExplorePage() {
}, []); }, []);
const handleJoinSuccess = (serverId: string) => { const handleJoinSuccess = (serverId: string) => {
setShowExplore(false);
setCurrentServer(serverId); setCurrentServer(serverId);
navigate(`/channels/${serverId}`); navigate(`/channels/${serverId}`);
}; };
@@ -183,14 +183,15 @@ export function AppLayout() {
// Handle route params // Handle route params
useEffect(() => { useEffect(() => {
if (serverId === '@me') { if (serverId === '@me') {
if (!useUIStore.getState().showExplore) { setShowDms(true);
setShowDms(true);
}
setCurrentServer(null); setCurrentServer(null);
} else if (serverId) { } else if (serverId) {
setShowDms(false); setShowDms(false);
setCurrentServer(serverId); setCurrentServer(serverId);
loadServerDetail(serverId); loadServerDetail(serverId);
} else {
setShowDms(false);
setCurrentServer(null);
} }
}, [serverId, setCurrentServer, loadServerDetail, setShowDms]); }, [serverId, setCurrentServer, loadServerDetail, setShowDms]);
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { useServerStore, getChannelOrigin } from '../../stores/serverStore'; import { useServerStore, getChannelOrigin } from '../../stores/serverStore';
import { useChatStore } from '../../stores/chatStore'; import { useChatStore } from '../../stores/chatStore';
import { useUIStore } from '../../stores/uiStore'; import { useUIStore } from '../../stores/uiStore';
@@ -35,6 +35,7 @@ export function ChannelSidebar() {
const toggleMic = useVoiceStore((s) => s.toggleMic); const toggleMic = useVoiceStore((s) => s.toggleMic);
const toggleDeafen = useVoiceStore((s) => s.toggleDeafen); const toggleDeafen = useVoiceStore((s) => s.toggleDeafen);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const handleMicToggle = async () => { const handleMicToggle = async () => {
toggleMic(); toggleMic();
@@ -142,7 +143,7 @@ export function ChannelSidebar() {
<div <div
onClick={handleHomeClick} onClick={handleHomeClick}
className={`flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group ${ className={`flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group ${
!currentChannelId !currentChannelId && location.pathname !== '/explore'
? 'bg-interactive-selected text-white' ? 'bg-interactive-selected text-white'
: 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary' : 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary'
}`} }`}
@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState, useCallback } from 'react'; import React, { useEffect, useRef, useState, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { useServerStore } from '../../stores/serverStore'; import { useServerStore } from '../../stores/serverStore';
import { useChatStore } from '../../stores/chatStore'; import { useChatStore } from '../../stores/chatStore';
import { useUIStore } from '../../stores/uiStore'; import { useUIStore } from '../../stores/uiStore';
@@ -30,7 +31,8 @@ export function MainContent() {
const isLiveKitConnected = useVoiceStore((s) => s.isLiveKitConnected); const isLiveKitConnected = useVoiceStore((s) => s.isLiveKitConnected);
const connectionError = useVoiceStore((s) => s.connectionError); const connectionError = useVoiceStore((s) => s.connectionError);
const showDms = useUIStore((s) => s.showDms); const showDms = useUIStore((s) => s.showDms);
const showExplore = useUIStore((s) => s.showExplore); const location = useLocation();
const isExplorePage = location.pathname === '/explore';
const activeDmCall = useVoiceStore((s) => s.activeDmCall); const activeDmCall = useVoiceStore((s) => s.activeDmCall);
const outgoingCall = useVoiceStore((s) => s.outgoingCall); const outgoingCall = useVoiceStore((s) => s.outgoingCall);
const dmChannels = useServerStore((s) => s.dmChannels); const dmChannels = useServerStore((s) => s.dmChannels);
@@ -62,9 +64,9 @@ export function MainContent() {
const channel = channels.find(c => c.id === currentChannelId); const channel = channels.find(c => c.id === currentChannelId);
const isVoiceChannel = channel?.type === 'voice' || channel?.type === 'video'; const isVoiceChannel = channel?.type === 'voice' || channel?.type === 'video';
if (showDms || showExplore || !currentServerId) { if (showDms || isExplorePage || !currentServerId) {
if (!currentChannelId) { if (!currentChannelId) {
if (showExplore) return <ExplorePage />; if (isExplorePage) return <ExplorePage />;
return <FriendsPage />; return <FriendsPage />;
} }
@@ -1,5 +1,5 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { useServerStore } from '../../stores/serverStore'; import { useServerStore } from '../../stores/serverStore';
import { useChatStore } from '../../stores/chatStore'; import { useChatStore } from '../../stores/chatStore';
import { useUIStore } from '../../stores/uiStore'; import { useUIStore } from '../../stores/uiStore';
@@ -120,14 +120,13 @@ export function ServerSidebar() {
const dmChannels = useServerStore((s) => s.dmChannels); const dmChannels = useServerStore((s) => s.dmChannels);
const showDms = useUIStore((s) => s.showDms); const showDms = useUIStore((s) => s.showDms);
const setShowDms = useUIStore((s) => s.setShowDms); const setShowDms = useUIStore((s) => s.setShowDms);
const showExplore = useUIStore((s) => s.showExplore);
const setShowExplore = useUIStore((s) => s.setShowExplore);
const openModal = useUIStore((s) => s.openModal); const openModal = useUIStore((s) => s.openModal);
const addToast = useUIStore((s) => s.addToast); const addToast = useUIStore((s) => s.addToast);
const setCurrentChannel = useChatStore((s) => s.setCurrentChannel); const setCurrentChannel = useChatStore((s) => s.setCurrentChannel);
const unreadChannels = useChatStore((s) => s.unreadChannels); const unreadChannels = useChatStore((s) => s.unreadChannels);
const instances = useInstanceStore((s) => s.instances); const instances = useInstanceStore((s) => s.instances);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
// Group servers by origin // Group servers by origin
const groupedServers = useMemo(() => { const groupedServers = useMemo(() => {
@@ -182,7 +181,6 @@ export function ServerSidebar() {
} }
setCurrentServer(serverId); setCurrentServer(serverId);
setShowDms(false); setShowDms(false);
setShowExplore(false);
navigate(`/channels/${serverId}`); navigate(`/channels/${serverId}`);
}; };
@@ -194,10 +192,9 @@ export function ServerSidebar() {
}; };
const handleExploreClick = () => { const handleExploreClick = () => {
setShowExplore(true);
setCurrentServer(null); setCurrentServer(null);
setCurrentChannel(null); setCurrentChannel(null);
navigate('/channels/@me'); navigate('/explore');
}; };
return ( return (
@@ -276,7 +273,7 @@ export function ServerSidebar() {
<SidebarItem <SidebarItem
id="explore" id="explore"
name="Explore Servers" name="Explore Servers"
active={showExplore} active={location.pathname === '/explore'}
onClick={handleExploreClick} onClick={handleExploreClick}
type="action" type="action"
actionType="explore" actionType="explore"
+1 -5
View File
@@ -28,7 +28,6 @@ interface UIState {
modalData: Record<string, unknown>; modalData: Record<string, unknown>;
isMobile: boolean; isMobile: boolean;
showDms: boolean; showDms: boolean;
showExplore: boolean;
imagePreviewUrl: string | null; imagePreviewUrl: string | null;
userProfilePopout: { userProfilePopout: {
user: User | null; user: User | null;
@@ -41,7 +40,6 @@ interface UIState {
closeModal: () => void; closeModal: () => void;
setIsMobile: (isMobile: boolean) => void; setIsMobile: (isMobile: boolean) => void;
setShowDms: (show: boolean) => void; setShowDms: (show: boolean) => void;
setShowExplore: (show: boolean) => void;
openImagePreview: (url: string) => void; openImagePreview: (url: string) => void;
closeImagePreview: () => void; closeImagePreview: () => void;
openUserProfile: (user: User, position: { top: number; left: number }) => void; openUserProfile: (user: User, position: { top: number; left: number }) => void;
@@ -66,7 +64,6 @@ export const useUIStore = create<UIState>()(
modalData: {}, modalData: {},
isMobile: false, isMobile: false,
showDms: false, showDms: false,
showExplore: false,
imagePreviewUrl: null, imagePreviewUrl: null,
userProfilePopout: { userProfilePopout: {
user: null, user: null,
@@ -92,8 +89,7 @@ export const useUIStore = create<UIState>()(
} }
}, },
setShowDms: (show) => set({ showDms: show, ...(show ? { showExplore: false } : {}) }), setShowDms: (show) => set({ showDms: show }),
setShowExplore: (show) => set({ showExplore: show, ...(show ? { showDms: false } : {}) }),
openImagePreview: (url) => set({ activeModal: 'imagePreview', imagePreviewUrl: url }), openImagePreview: (url) => set({ activeModal: 'imagePreview', imagePreviewUrl: url }),
closeImagePreview: () => set({ activeModal: null, imagePreviewUrl: null }), closeImagePreview: () => set({ activeModal: null, imagePreviewUrl: null }),