Files
DiscordClone/packages/shared/src/components/layout/GuildsLayout.tsx
Bryan1029384756 0153444817
Some checks failed
Build and Release / build-and-release (push) Has been cancelled
1.0.90
2026-04-15 20:51:49 -05:00

115 lines
4.1 KiB
TypeScript

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<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(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 (
<FileUploadDropZone
channelName={channelName}
disabled={dropDisabled}
onDropFiles={(files) => {
if (!channelId) return;
window.dispatchEvent(
new CustomEvent('brycord:drop-files', {
detail: { channelId, files },
}),
);
}}
>
<div className={styles.wrapper} data-mobile-mode={mobileMode} ref={wrapperRef}>
<div className={styles.container} ref={containerRef}>
<div className={styles.guildList}>
<GuildList />
</div>
<div className={styles.sidebar}>
{hasServer ? <GuildNavbar /> : <DMLayout />}
</div>
<div className={styles.sidebarDivider} />
<div className={styles.content}>
{rememberedChannelId ? (
<ChannelView channelId={rememberedChannelId} />
) : (
children
)}
</div>
<div className={styles.userAreaWrapper}>
<UserArea />
</div>
</div>
<MobileBottomNav />
</div>
</FileUploadDropZone>
);
}