diff --git a/packages/web/src/components/layout/MobileBottomNav.tsx b/packages/web/src/components/layout/MobileBottomNav.tsx new file mode 100644 index 00000000..2129ab75 --- /dev/null +++ b/packages/web/src/components/layout/MobileBottomNav.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { useUIStore } from '../../stores/uiStore'; +import { useChatStore } from '../../stores/chatStore'; +import { useAuthStore } from '../../stores/authStore'; +import { useSocialStore } from '../../stores/socialStore'; +import { useSpaceStore } from '../../stores/spaceStore'; +import { useNavigate } from 'react-router-dom'; + +export function MobileBottomNav() { + const mobileScreen = useUIStore((s) => s.mobileScreen); + const mobileStack = useUIStore((s) => s.mobileStack); + const setMobileTab = useUIStore((s) => s.setMobileTab); + const lastChannelPerSpace = useUIStore((s) => s.lastChannelPerSpace); + const navigate = useNavigate(); + + // Badge data + const unreadChannels = useChatStore((s) => s.unreadChannels); + const voiceChannelIds = useSpaceStore((s) => s.voiceChannelIds); + const dmChannels = useSpaceStore((s) => s.dmChannels); + const readStates = useChatStore((s) => s.readStates); + const requests = useSocialStore((s) => s.requests); + const authUser = useAuthStore((s) => s.user); + + // Hide when any screen is pushed (chat, settings, friends, etc.) + if (mobileStack.length > 0) return null; + + // Compute unread DM count + const unreadDmCount = dmChannels.filter((dm) => { + const lastMsgId = dm.lastMessage?.id; + const readState = readStates.get(dm.id); + return lastMsgId && (!readState || readState < lastMsgId); + }).length; + + // Compute whether any space has unread text channels + 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 handleTab = (tab: 'spaces' | 'dms' | 'you') => { + setMobileTab(tab); + if (tab === 'dms') navigate('/channels/@me'); + if (tab === 'spaces') { + const lastSpace = Object.keys(lastChannelPerSpace)[0]; + if (lastSpace) navigate(`/channels/${lastSpace}`); + else navigate('/'); + } + }; + + const tabs = [ + { + id: 'spaces' as const, + label: 'Spaces', + icon: ( + + + + ), + badge: hasUnreadSpaces ? ('dot' as const) : null, + }, + { + id: 'dms' as const, + label: 'DMs', + icon: ( + + + + ), + badge: unreadDmCount > 0 ? unreadDmCount : null, + }, + { + id: 'you' as const, + label: 'You', + icon: ( + + + + ), + badge: pendingIncoming.length > 0 ? ('dot' as const) : null, + }, + ]; + + return ( + + ); +}