import React, { useState, useMemo, useEffect } from 'react'; import { useUIStore } from '../../stores/uiStore'; import { useSpaceStore } from '../../stores/spaceStore'; import { useChatStore } from '../../stores/chatStore'; import { useVoiceStore } from '../../stores/voiceStore'; import { useAuthStore } from '../../stores/authStore'; import { useNavigate } from 'react-router-dom'; import { getSpaceGradient } from '../../utils/gradients'; import { hasPermissionBit, PermissionBits } from '../../utils/permissions'; import type { Channel } from '@backspace/shared'; import { Mascot } from '../ui/Mascot'; export function MobileSpacesScreen() { const spaces = useSpaceStore((s) => s.spaces); const channels = useSpaceStore((s) => s.channels); const categories = useSpaceStore((s) => s.categories); const currentSpaceId = useSpaceStore((s) => s.currentSpaceId); const loadSpaceDetail = useSpaceStore((s) => s.loadSpaceDetail); const setCurrentSpace = useSpaceStore((s) => s.setCurrentSpace); const channelToSpaceMap = useSpaceStore((s) => s.channelToSpaceMap); const voiceChannelIds = useSpaceStore((s) => s.voiceChannelIds); const spacePermissions = useSpaceStore((s) => s.spacePermissions); const channelPermissions = useSpaceStore((s) => s.channelPermissions); const unreadChannels = useChatStore((s) => s.unreadChannels); const currentChannelId = useChatStore((s) => s.currentChannelId); const voiceUsers = useVoiceStore((s) => s.voiceUsers); const currentVoiceChannelId = useVoiceStore((s) => s.currentVoiceChannelId); const setCurrentVoiceChannel = useVoiceStore((s) => s.setCurrentVoiceChannel); const pushMobileScreen = useUIStore((s) => s.pushMobileScreen); const setMobileTab = useUIStore((s) => s.setMobileTab); const openModal = useUIStore((s) => s.openModal); const setLastChannel = useUIStore((s) => s.setLastChannel); const navigate = useNavigate(); const user = useAuthStore((s) => s.user); // Local state const [selectedSpaceId, setSelectedSpaceId] = useState(currentSpaceId); const [collapsedCategories, setCollapsedCategories] = useState>(new Set()); const [showAddSheet, setShowAddSheet] = useState(false); // Sync selected space with store's current space useEffect(() => { if (selectedSpaceId) { setCurrentSpace(selectedSpaceId); loadSpaceDetail(selectedSpaceId); } }, [selectedSpaceId, setCurrentSpace, loadSpaceDetail]); // Auto-select first space if none selected useEffect(() => { if (!selectedSpaceId && spaces.length > 0 && spaces[0]) { setSelectedSpaceId(spaces[0].id); } }, [selectedSpaceId, spaces]); // Check if loaded channels belong to selected space const spaceChannels = useMemo(() => { if (!selectedSpaceId) return []; return channels.filter(c => channelToSpaceMap.get(c.id) === selectedSpaceId); }, [channels, channelToSpaceMap, selectedSpaceId]); // Group channels by category const channelsByCategory = useMemo(() => { const map = new Map(); for (const ch of spaceChannels) { if (!ch.categoryId) continue; let arr = map.get(ch.categoryId); if (!arr) { arr = []; map.set(ch.categoryId, arr); } arr.push(ch); } for (const [key, arr] of map) { map.set(key, arr.sort((a, b) => a.position - b.position)); } return map; }, [spaceChannels]); const uncategorizedChannels = useMemo(() => spaceChannels .filter(c => !c.categoryId) .sort((a, b) => a.position - b.position), [spaceChannels] ); const sortedCategories = useMemo(() => [...categories].sort((a, b) => a.position - b.position), [categories] ); // Check if space has unread text channels const spaceHasUnread = (spaceId: string): boolean => { for (const chId of unreadChannels) { if (channelToSpaceMap.get(chId) === spaceId && !voiceChannelIds.has(chId)) { return true; } } return false; }; const handleSpaceSelect = (spaceId: string) => { setSelectedSpaceId(spaceId); setCollapsedCategories(new Set()); }; const handleChannelTap = (channel: Channel) => { if (channel.type === 'voice') { setCurrentVoiceChannel(channel.id); return; } if (selectedSpaceId) { setLastChannel(selectedSpaceId, channel.id); navigate(`/channels/${selectedSpaceId}/${channel.id}`); } pushMobileScreen('channel-chat', { channelId: channel.id, spaceId: selectedSpaceId || '', }); }; const toggleCategory = (categoryId: string) => { setCollapsedCategories(prev => { const next = new Set(prev); if (next.has(categoryId)) next.delete(categoryId); else next.add(categoryId); return next; }); }; const selectedSpace = spaces.find(s => s.id === selectedSpaceId); const myPerms = selectedSpaceId ? spacePermissions.get(selectedSpaceId) : undefined; const canInvite = hasPermissionBit(myPerms, PermissionBits.CREATE_INVITE); const canManageSpace = hasPermissionBit(myPerms, PermissionBits.MANAGE_SPACE); const renderChannelItem = (channel: Channel) => { const isVoice = channel.type === 'voice'; const isActive = !isVoice && currentChannelId === channel.id; const isUnread = unreadChannels.has(channel.id) && !isActive; const isInVoice = currentVoiceChannelId === channel.id; const vUsers = voiceUsers.get(channel.id) || []; const canView = hasPermissionBit(channelPermissions.get(channel.id), PermissionBits.VIEW_CHANNEL); if (canView === false) return null; return ( ); }; return (
{/* Space strip */}
{/* Home / DMs button */}
{/* Space icons */} {spaces.map(space => { const isSelected = space.id === selectedSpaceId; const hasUnread = spaceHasUnread(space.id); const iconUrl = space.icon ? (space.icon.startsWith('http') || space.icon.startsWith('/') ? space.icon : `/api/uploads/${space.icon}`) : null; const grad = getSpaceGradient(space.id, space.name, space.avatarColor); return (
{/* Unread / selected pill */}
); })}
{/* Add space button */}
{/* Channel list */}
{/* Space header */} {selectedSpace && (

{selectedSpace.name}

{canInvite && ( )} {canManageSpace && ( )}
)} {/* Channel list */}
{/* Uncategorized channels */} {uncategorizedChannels.map(renderChannelItem)} {/* Categorized channels */} {sortedCategories.map(category => { const catChannels = channelsByCategory.get(category.id) || []; if (catChannels.length === 0) return null; const isCollapsed = collapsedCategories.has(category.id); return (
{!isCollapsed && catChannels.map(renderChannelItem)}
); })} {/* Empty state */} {spaceChannels.length === 0 && selectedSpaceId && (

No channels yet.

)}
{/* Add Space bottom sheet */} {showAddSheet && ( <>
setShowAddSheet(false)} />
)}
); }