From ed70099963ba81ba1917d9f6b847ea25d0a88813 Mon Sep 17 00:00:00 2001
From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com>
Date: Tue, 17 Mar 2026 15:54:39 +0100
Subject: [PATCH] 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
---
.../src/components/NotificationController.tsx | 3 +-
.../src/components/layout/MobileBottomNav.tsx | 2 +-
.../components/layout/MobileChatScreen.tsx | 6 +-
.../layout/MobileSettingsScreen.tsx | 2 +-
.../web/src/components/layout/MobileShell.tsx | 3 +-
.../components/layout/MobileSpacesScreen.tsx | 6 +-
.../layout/MobileVoiceFullScreen.tsx | 12 ++--
.../components/layout/MobileVoiceMiniBar.tsx | 9 +--
.../src/components/layout/MobileYouScreen.tsx | 7 ++-
packages/web/src/components/ui/Modal.tsx | 62 ++++++++++++++++++-
packages/web/src/hooks/useSwipeGesture.ts | 2 +
11 files changed, 89 insertions(+), 25 deletions(-)
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() {