diff --git a/packages/web/src/components/NotificationController.tsx b/packages/web/src/components/NotificationController.tsx index 65e356fa..b0ec1f0b 100644 --- a/packages/web/src/components/NotificationController.tsx +++ b/packages/web/src/components/NotificationController.tsx @@ -62,8 +62,7 @@ export function NotificationController() { ? message.content.replace(/[*_~`>#\-\[\]]/g, '').slice(0, 100) : 'Sent an attachment'; sendNotification(displayName, body, { - channelId: message.channelId ?? message.dmChannelId, - spaceId: message.dmChannelId ? '@me' : undefined, + channelId: message.channelId, }); break; // one notification per batch } diff --git a/packages/web/src/components/layout/MobileBottomNav.tsx b/packages/web/src/components/layout/MobileBottomNav.tsx index 2129ab75..29e1c1e6 100644 --- a/packages/web/src/components/layout/MobileBottomNav.tsx +++ b/packages/web/src/components/layout/MobileBottomNav.tsx @@ -35,7 +35,7 @@ export function MobileBottomNav() { const hasUnreadSpaces = Array.from(unreadChannels).some(chId => !voiceChannelIds.has(chId)); // Pending friend requests — filter for incoming only - const pendingIncoming = requests.filter(r => r.status === 'pending' && r.from_id !== authUser?.id); + const pendingIncoming = requests.filter(r => r.status === 'pending' && r.fromId !== authUser?.id); const handleTab = (tab: 'spaces' | 'dms' | 'you') => { setMobileTab(tab); diff --git a/packages/web/src/components/layout/MobileChatScreen.tsx b/packages/web/src/components/layout/MobileChatScreen.tsx index 69267cd3..12d4057a 100644 --- a/packages/web/src/components/layout/MobileChatScreen.tsx +++ b/packages/web/src/components/layout/MobileChatScreen.tsx @@ -75,12 +75,12 @@ export function MobileChatScreen({ params }: MobileChatScreenProps) { {/* Messages */}
- + {channelId && }
{/* Typing indicator + Input */} - - + {channelId && } + {channelId && } ); } diff --git a/packages/web/src/components/layout/MobileSettingsScreen.tsx b/packages/web/src/components/layout/MobileSettingsScreen.tsx index d58e91b1..e42e245e 100644 --- a/packages/web/src/components/layout/MobileSettingsScreen.tsx +++ b/packages/web/src/components/layout/MobileSettingsScreen.tsx @@ -50,7 +50,7 @@ const sectionIcons: Record = { export function MobileSettingsScreen({ initialPanel }: MobileSettingsScreenProps) { const popMobileScreen = useUIStore((s) => s.popMobileScreen); const pushMobileScreen = useUIStore((s) => s.pushMobileScreen); - const isAdmin = useAuthStore((s) => s.user?.is_admin); + const isAdmin = useAuthStore((s) => s.user?.isAdmin); // If initialPanel is set, render that panel directly if (initialPanel) { diff --git a/packages/web/src/components/layout/MobileShell.tsx b/packages/web/src/components/layout/MobileShell.tsx index d050f588..dff8b841 100644 --- a/packages/web/src/components/layout/MobileShell.tsx +++ b/packages/web/src/components/layout/MobileShell.tsx @@ -63,7 +63,8 @@ export function MobileShell() { const path = location.pathname; const match = path.match(/^\/channels\/([^/]+)\/([^/]+)$/); if (match && mobileStack.length === 0) { - const [, spaceId, channelId] = match; + const spaceId = match[1] ?? ''; + const channelId = match[2] ?? ''; if (spaceId === '@me') { pushMobileScreen('channel-chat', { channelId, spaceId: '@me' }); } else { diff --git a/packages/web/src/components/layout/MobileSpacesScreen.tsx b/packages/web/src/components/layout/MobileSpacesScreen.tsx index c5406d8b..5f08c43b 100644 --- a/packages/web/src/components/layout/MobileSpacesScreen.tsx +++ b/packages/web/src/components/layout/MobileSpacesScreen.tsx @@ -27,7 +27,7 @@ export function MobileSpacesScreen() { const voiceUsers = useVoiceStore((s) => s.voiceUsers); const currentVoiceChannelId = useVoiceStore((s) => s.currentVoiceChannelId); - const joinChannel = useVoiceStore((s) => s.joinChannel); + const setCurrentVoiceChannel = useVoiceStore((s) => s.setCurrentVoiceChannel); const pushMobileScreen = useUIStore((s) => s.pushMobileScreen); const setMobileTab = useUIStore((s) => s.setMobileTab); @@ -52,7 +52,7 @@ export function MobileSpacesScreen() { // Auto-select first space if none selected useEffect(() => { - if (!selectedSpaceId && spaces.length > 0) { + if (!selectedSpaceId && spaces.length > 0 && spaces[0]) { setSelectedSpaceId(spaces[0].id); } }, [selectedSpaceId, spaces]); @@ -107,7 +107,7 @@ export function MobileSpacesScreen() { const handleChannelTap = (channel: Channel) => { if (channel.type === 'voice') { - joinChannel(channel.id); + setCurrentVoiceChannel(channel.id); return; } if (selectedSpaceId) { diff --git a/packages/web/src/components/layout/MobileVoiceFullScreen.tsx b/packages/web/src/components/layout/MobileVoiceFullScreen.tsx index 24945d92..04c01183 100644 --- a/packages/web/src/components/layout/MobileVoiceFullScreen.tsx +++ b/packages/web/src/components/layout/MobileVoiceFullScreen.tsx @@ -13,11 +13,11 @@ export function MobileVoiceFullScreen() { const isDeafened = useVoiceStore((s) => s.isDeafened); const isCameraOn = useVoiceStore((s) => s.isCameraOn); const isScreenSharing = useVoiceStore((s) => s.isScreenSharing); - const toggleMute = useVoiceStore((s) => s.toggleMute); + const toggleMute = useVoiceStore((s) => s.toggleMic); const toggleDeafen = useVoiceStore((s) => s.toggleDeafen); const toggleCamera = useVoiceStore((s) => s.toggleCamera); const toggleScreenShare = useVoiceStore((s) => s.toggleScreenShare); - const leaveChannel = useVoiceStore((s) => s.leaveChannel); + const leaveVoice = useVoiceStore((s) => s.leaveVoice); const voiceUsers = useVoiceStore((s) => s.voiceUsers); const voiceUserStates = useVoiceStore((s) => s.voiceUserStates); @@ -62,9 +62,9 @@ export function MobileVoiceFullScreen() { const member = members.find(m => m.userId === userId); if (member) { return { - name: member.nickname ?? member.displayName ?? member.username, - avatar: member.avatar ? `/api/uploads/${member.avatar}` : null, - avatarColor: member.avatarColor, + name: member.nickname ?? member.user?.displayName ?? member.user?.username ?? 'User', + avatar: member.user?.avatar ? `/api/uploads/${member.user.avatar}` : null, + avatarColor: member.user?.avatarColor ?? null, }; } // Check DM members @@ -82,7 +82,7 @@ export function MobileVoiceFullScreen() { }; const handleDisconnect = () => { - leaveChannel(); + leaveVoice(); popMobileScreen(); }; diff --git a/packages/web/src/components/layout/MobileVoiceMiniBar.tsx b/packages/web/src/components/layout/MobileVoiceMiniBar.tsx index 776557d1..0a03b671 100644 --- a/packages/web/src/components/layout/MobileVoiceMiniBar.tsx +++ b/packages/web/src/components/layout/MobileVoiceMiniBar.tsx @@ -10,10 +10,10 @@ export function MobileVoiceMiniBar() { const currentVoiceChannelId = useVoiceStore((s) => s.currentVoiceChannelId); const isMuted = useVoiceStore((s) => s.isMuted); const isDeafened = useVoiceStore((s) => s.isDeafened); - const toggleMute = useVoiceStore((s) => s.toggleMute); + const toggleMute = useVoiceStore((s) => s.toggleMic); const toggleDeafen = useVoiceStore((s) => s.toggleDeafen); const voiceUsers = useVoiceStore((s) => s.voiceUsers); - const leaveChannel = useVoiceStore((s) => s.leaveChannel); + const leaveVoice = useVoiceStore((s) => s.leaveVoice); const channels = useSpaceStore((s) => s.channels); const dmChannels = useSpaceStore((s) => s.dmChannels); @@ -21,7 +21,8 @@ export function MobileVoiceMiniBar() { if (!currentVoiceChannelId) return null; // Don't show mini-bar if voice full-screen is on top of the stack - const topScreen = mobileStack.length > 0 ? mobileStack[mobileStack.length - 1].screen : null; + const topEntry = mobileStack.length > 0 ? mobileStack[mobileStack.length - 1] : undefined; + const topScreen = topEntry?.screen ?? null; if (topScreen === 'voice-full') return null; // Resolve channel name @@ -91,7 +92,7 @@ export function MobileVoiceMiniBar() { + + )} +
+ {children} +
+ + ); + } + + // Mobile bottom sheet style + if (isMobile && mobileStyle === 'sheet') { + return ( +
+
+
+ {title && ( +
+

{title}

+ +
+ )} +
+ {children} +
+
+
+ ); + } + + // Default centered dialog (desktop and mobile default) return (
{ if (!enabled) return; const touch = e.touches[0]; + if (!touch) return; if (touch.clientX <= edgeThreshold) { touchStartRef.current = { x: touch.clientX, y: touch.clientY }; swipingRef.current = false; @@ -28,6 +29,7 @@ export function useSwipeGesture({ const handleTouchMove = useCallback((e: TouchEvent) => { if (!touchStartRef.current) return; const touch = e.touches[0]; + if (!touch) return; const dx = touch.clientX - touchStartRef.current.x; const dy = touch.clientY - touchStartRef.current.y;