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:
@@ -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"
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
|||||||
Reference in New Issue
Block a user