fix(mobile): resolve all TypeScript errors from integration

- Fix voiceStore method names (toggleMic, leaveVoice, setCurrentVoiceChannel)
- Fix MemberWithUser nested user property access
- Pass required channelId props to MessageList, TypingIndicator, MessageInput
- Fix ConfirmDialog props (isOpen, description, variant, onClose)
- Add mobileStyle prop to Modal component (fullscreen + sheet modes)
- Fix fromId/isAdmin camelCase property names
- Guard possibly undefined touch events and array accesses
- Fix notification channelId resolution
This commit is contained in:
Jannis Braun
2026-03-17 15:54:39 +01:00
parent 7619b091da
commit ed70099963
11 changed files with 89 additions and 25 deletions
@@ -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);
@@ -75,12 +75,12 @@ export function MobileChatScreen({ params }: MobileChatScreenProps) {
{/* Messages */}
<div className="flex-1 overflow-hidden">
<MessageList />
{channelId && <MessageList channelId={channelId} />}
</div>
{/* Typing indicator + Input */}
<TypingIndicator />
<MessageInput />
{channelId && <TypingIndicator channelId={channelId} />}
{channelId && <MessageInput channelId={channelId} channelName={channelName} />}
</div>
);
}
@@ -50,7 +50,7 @@ const sectionIcons: Record<string, React.ReactNode> = {
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) {
@@ -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 {
@@ -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) {
@@ -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();
};
@@ -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() {
</button>
<button
onClick={(e) => { e.stopPropagation(); leaveChannel(); }}
onClick={(e) => { e.stopPropagation(); leaveVoice(); }}
className="w-8 h-8 rounded-full flex items-center justify-center bg-accent-rose/20 text-accent-rose hover:bg-accent-rose/30 transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@@ -142,12 +142,13 @@ export function MobileYouScreen() {
{showLogoutConfirm && (
<ConfirmDialog
isOpen={true}
title="Log Out"
message="Are you sure you want to log out?"
description="Are you sure you want to log out?"
confirmLabel="Log Out"
onConfirm={() => { setShowLogoutConfirm(false); logout(); }}
onCancel={() => setShowLogoutConfirm(false)}
destructive
onClose={() => setShowLogoutConfirm(false)}
variant="danger"
/>
)}
</div>