import { useQuery } from 'convex/react'; import { useRef, type ReactNode } from 'react'; import { useLocation } from 'react-router-dom'; import { api } from '../../../../../convex/_generated/api'; import { useIsMobile } from '../../hooks/useIsMobile'; import { useMobileSwipeNav } from '../../hooks/useMobileSwipeNav'; import { FileUploadDropZone } from '../channel/FileUploadDropZone'; import { ChannelView } from '../channel/ChannelView'; import { DMLayout } from '../dm/DMLayout'; import { GuildList } from './GuildList'; import { GuildNavbar } from './GuildNavbar'; import { MobileBottomNav } from './MobileBottomNav'; import { UserArea } from './UserArea'; import styles from './GuildsLayout.module.css'; interface GuildsLayoutProps { children: ReactNode; } /** * Parse the current pathname to decide which mobile view to show. At * ≤768px exactly one of the three columns is visible at a time. * /channels/@me → nav (DM list) * /channels/@me/:channelId → chat * /channels/:serverId → nav (channel list) * /channels/:serverId/:channelId → chat * /channels/you → you (mobile profile tab) */ function detectMobileMode(pathname: string): 'nav' | 'chat' | 'you' { const segments = pathname.split('/').filter(Boolean); if (segments[0] !== 'channels') return 'nav'; if (segments[1] === 'you') return 'you'; return segments.length >= 3 && !!segments[2] ? 'chat' : 'nav'; } export function GuildsLayout({ children }: GuildsLayoutProps) { const location = useLocation(); const segments = location.pathname.split('/').filter(Boolean); // /channels/:serverId/... — serverId is segments[1], '@me' for DMs const serverId = segments[0] === 'channels' ? segments[1] : undefined; const channelId = segments[0] === 'channels' ? segments[2] : undefined; const hasServer = !!serverId && serverId !== '@me' && serverId !== 'you'; const mobileMode = detectMobileMode(location.pathname); const isMobile = useIsMobile(); const wrapperRef = useRef(null); const containerRef = useRef(null); useMobileSwipeNav(containerRef, wrapperRef); // On mobile nav mode, pre-render the last-viewed channel so the // swipe preview shows actual chat content instead of the "Select a // channel" placeholder. The remembered channelId comes from the // same localStorage key the swipe hook writes. const rememberedChannelId = isMobile && mobileMode === 'nav' && serverId ? (() => { try { return localStorage.getItem(`brycord:lastChannel:${serverId}`); } catch { return null; } })() : null; // Look up the current channel's name + type so the drop overlay // reads "Upload to #channel-name". Drops are disabled when there's // no channel in the URL (DM home, `/you` profile) and on voice // channels — they have no composer to attach to. const channel = useQuery( api.channels.get, channelId ? { id: channelId as any } : 'skip', ); const channelName = (channel && (channel as any).name) || 'channel'; const channelType = (channel && (channel as any).type) || undefined; const dropDisabled = !channelId || channelType === 'voice' || channelType === 'category'; return ( { if (!channelId) return; window.dispatchEvent( new CustomEvent('brycord:drop-files', { detail: { channelId, files }, }), ); }} >
{hasServer ? : }
{rememberedChannelId ? ( ) : ( children )}
); }