Skip to content

Commit 433652f

Browse files
authored
Merge pull request #28 from 8JP8/fix-frontend-ux-routing-5747148903053641978
Fix frontend UX, routing, and mobile interactions
2 parents 5f8b07e + 6266bf7 commit 433652f

8 files changed

Lines changed: 205 additions & 64 deletions

File tree

frontend/components/ChatRoom/ChatRoomContainer.tsx

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -990,18 +990,6 @@ const ChatRoomContainer: React.FC<ChatRoomContainerProps> = ({
990990

991991
{/* Action Buttons */}
992992
<div className="flex flex-wrap sm:flex-nowrap items-center gap-2 mt-2 sm:mt-0 w-full sm:w-auto">
993-
<button
994-
onClick={() => handleFollowChat(room.id)}
995-
disabled={followLoading}
996-
className={`p-2 rounded-lg transition-colors ${isFollowing
997-
? 'text-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/30'
998-
: 'text-gray-400 hover:text-blue-500 hover:bg-gray-100 dark:hover:bg-gray-700'
999-
}`}
1000-
title={isFollowing ? (t('chat.unfollow') || 'Unfollow') : (t('chat.follow') || 'Follow')}
1001-
>
1002-
{isFollowing ? <BellOff size={20} /> : <Bell size={20} />}
1003-
</button>
1004-
1005993
{/* VOIP Button */}
1006994
{!room.is_public && (roomData?.voip_enabled ?? room.voip_enabled) && (
1007995
<div className="flex-1 sm:flex-none">
@@ -1015,34 +1003,49 @@ const ChatRoomContainer: React.FC<ChatRoomContainerProps> = ({
10151003
/>
10161004
</div>
10171005
)}
1018-
<div className="flex flex-col min-[925px]:flex-row gap-2 flex-1 sm:flex-none">
1019-
{isOwner && (
1020-
<button
1021-
onClick={() => {
1022-
setMembersModalMode('manage');
1023-
setShowMembersModal(true);
1024-
}}
1025-
className="flex-1 px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center justify-center gap-2 transition-colors whitespace-nowrap min-w-fit"
1026-
title={t('chat.manageChat') || 'Manage Chat'}
1027-
>
1028-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1029-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
1030-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
1031-
</svg>
1032-
<span className="truncate hidden min-[1050px]:inline">{t('chat.manageChat') || 'Manage'}</span>
1033-
</button>
1034-
)}
1035-
{onBack && (
1036-
<button
1037-
onClick={onBack}
1038-
className="flex-1 px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center justify-center gap-2 transition-colors whitespace-nowrap min-w-fit"
1039-
>
1040-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1041-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
1042-
</svg>
1043-
<span className="hidden min-[1050px]:inline">{t('common.back')}</span>
1044-
</button>
1045-
)}
1006+
1007+
<div className="flex items-center gap-2 flex-1 sm:flex-none w-full sm:w-auto">
1008+
<button
1009+
onClick={() => handleFollowChat(room.id)}
1010+
disabled={followLoading}
1011+
className={`p-2 rounded-lg transition-colors flex-none ${isFollowing
1012+
? 'text-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/30'
1013+
: 'text-gray-400 hover:text-blue-500 hover:bg-gray-100 dark:hover:bg-gray-700'
1014+
}`}
1015+
title={isFollowing ? (t('chat.unfollow') || 'Unfollow') : (t('chat.follow') || 'Follow')}
1016+
>
1017+
{isFollowing ? <BellOff size={20} /> : <Bell size={20} />}
1018+
</button>
1019+
1020+
<div className="flex flex-row gap-2 flex-1 min-w-0">
1021+
{isOwner && (
1022+
<button
1023+
onClick={() => {
1024+
setMembersModalMode('manage');
1025+
setShowMembersModal(true);
1026+
}}
1027+
className="flex-1 px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center justify-center gap-2 transition-colors whitespace-nowrap min-w-fit"
1028+
title={t('chat.manageChat') || 'Manage Chat'}
1029+
>
1030+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1031+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
1032+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
1033+
</svg>
1034+
<span className="truncate hidden min-[1050px]:inline">{t('chat.manageChat') || 'Manage'}</span>
1035+
</button>
1036+
)}
1037+
{onBack && (
1038+
<button
1039+
onClick={onBack}
1040+
className="flex-1 px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center justify-center gap-2 transition-colors whitespace-nowrap min-w-fit"
1041+
>
1042+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1043+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
1044+
</svg>
1045+
<span className="hidden min-[1050px]:inline">{t('common.back')}</span>
1046+
</button>
1047+
)}
1048+
</div>
10461049
</div>
10471050
</div>
10481051
</div>

frontend/components/Post/PostDetail.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const PostDetail: React.FC<PostDetailProps> = ({ postId }) => {
4545
const { t, language } = useLanguage();
4646
const [post, setPost] = useState<Post | null>(null);
4747
const [loading, setLoading] = useState(true);
48+
const [notFound, setNotFound] = useState(false);
4849
const [isFollowing, setIsFollowing] = useState(false);
4950
const [followLoading, setFollowLoading] = useState(false);
5051
const [contextMenu, setContextMenu] = useState<{ x: number, y: number } | null>(null);
@@ -136,13 +137,11 @@ const PostDetail: React.FC<PostDetailProps> = ({ postId }) => {
136137
if (response.data.success) {
137138
setPost(response.data.data);
138139
} else {
139-
toast.error(t('errors.postNotFound') || 'Post not found');
140-
router.push('/');
140+
setNotFound(true);
141141
}
142142
} catch (error) {
143143
console.error('Error fetching post:', error);
144-
toast.error(t('errors.loadingFailed') || 'Failed to load post');
145-
router.push('/');
144+
setNotFound(true);
146145
} finally {
147146
setLoading(false);
148147
}
@@ -231,6 +230,20 @@ const PostDetail: React.FC<PostDetailProps> = ({ postId }) => {
231230
);
232231
}
233232

233+
if (notFound) {
234+
return (
235+
<div className="flex flex-col items-center justify-center min-h-[50vh] text-center p-8">
236+
<h2 className="text-2xl font-bold theme-text-primary mb-4">{t('errors.postNotFound') || 'Post Not Found'}</h2>
237+
<button
238+
onClick={() => router.push('/')}
239+
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
240+
>
241+
{t('settings.backToDashboard') || 'Back to Dashboard'}
242+
</button>
243+
</div>
244+
);
245+
}
246+
234247
if (!post) {
235248
return null;
236249
}

frontend/components/UI/ContextMenu.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ items, onClose, x, y }) => {
108108

109109
return (
110110
<>
111+
<div
112+
className="fixed inset-0 z-40"
113+
onClick={onClose}
114+
/>
111115
<div
112116
ref={menuRef}
113117
className="fixed z-50 theme-bg-secondary border theme-border rounded-lg shadow-xl py-1 min-w-[180px]"

frontend/components/UI/GlobeBackground.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,10 @@ function GlobeBackgroundInner({ className = '' }: GlobeBackgroundProps) {
284284
return (
285285
<div
286286
className={`fixed inset-0 flex items-center justify-center overflow-hidden pointer-events-none ${className}`}
287-
style={{ transform: `translateY(${scrollY * 0.15}px)` }}
287+
style={{
288+
transform: `translateY(${scrollY * 0.15}px)`,
289+
touchAction: 'pan-y'
290+
}}
288291
>
289292
<div className="relative" style={{ width: 600, height: 600 }}>
290293
<canvas
@@ -301,6 +304,10 @@ function GlobeBackgroundInner({ className = '' }: GlobeBackgroundProps) {
301304
pointerInteracting.current = null;
302305
if (canvasRef.current) canvasRef.current.style.cursor = 'grab';
303306
}}
307+
onPointerCancel={() => {
308+
pointerInteracting.current = null;
309+
if (canvasRef.current) canvasRef.current.style.cursor = 'grab';
310+
}}
304311
onMouseMove={(e) => {
305312
if (pointerInteracting.current !== null) {
306313
const delta = e.clientX - pointerInteracting.current;

frontend/pages/about.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,6 @@ export default function About() {
122122
>
123123
{t('auth.login')} / {t('auth.register')}
124124
</Link>
125-
<Link
126-
href="/?startTour=true"
127-
className="px-8 py-4 bg-slate-800/80 hover:bg-slate-700 text-slate-200 rounded-full font-bold text-lg transition-all border border-slate-600 hover:border-slate-500 backdrop-blur-sm shadow-xl no-underline hover:no-underline hover:scale-105"
128-
>
129-
{t('about.takeATour') || 'Take the Tour'}
130-
</Link>
131125
</>
132126
)}
133127
</div>

frontend/pages/chat-room/[id].tsx

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,99 @@
1-
import { useEffect } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { useRouter } from 'next/router';
3+
import Layout from '@/components/Layout/Layout';
4+
import ChatRoomContainer from '@/components/ChatRoom/ChatRoomContainer';
35
import LoadingSpinner from '@/components/UI/LoadingSpinner';
6+
import { api, API_ENDPOINTS } from '@/utils/api';
7+
import { toast } from 'react-hot-toast';
48

5-
export default function ChatRoomRedirect() {
9+
interface ChatRoom {
10+
id: string;
11+
name: string;
12+
description: string;
13+
topic_id: string;
14+
owner_id: string;
15+
owner?: {
16+
id: string;
17+
username: string;
18+
};
19+
tags: string[];
20+
is_public: boolean;
21+
member_count: number;
22+
message_count: number;
23+
last_activity: string;
24+
user_is_member?: boolean;
25+
background_picture?: string;
26+
}
27+
28+
export default function ChatRoomPage() {
629
const router = useRouter();
730
const { id } = router.query;
31+
const [room, setRoom] = useState<ChatRoom | null>(null);
32+
const [loading, setLoading] = useState(true);
33+
const [error, setError] = useState(false);
834

935
useEffect(() => {
10-
if (id) {
11-
router.replace(`/?chatRoomId=${id}`);
36+
if (id && typeof id === 'string') {
37+
fetchRoom(id);
38+
} else if (router.isReady && !id) {
39+
// If ready but no ID? 404
40+
setError(true);
41+
setLoading(false);
42+
}
43+
}, [id, router.isReady]);
44+
45+
const fetchRoom = async (roomId: string) => {
46+
try {
47+
setLoading(true);
48+
const response = await api.get(API_ENDPOINTS.CHAT_ROOMS.GET(roomId));
49+
if (response.data.success) {
50+
setRoom(response.data.data);
51+
} else {
52+
setError(true);
53+
}
54+
} catch (error) {
55+
console.error('Error fetching chat room:', error);
56+
setError(true);
57+
} finally {
58+
setLoading(false);
1259
}
13-
}, [id, router]);
60+
};
61+
62+
if (error) {
63+
return (
64+
<Layout>
65+
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
66+
<h1 className="text-2xl font-bold mb-4 theme-text-primary">Chat Room Not Found</h1>
67+
<p className="theme-text-secondary mb-6">The chat room you are looking for does not exist or you do not have permission to view it.</p>
68+
<button
69+
onClick={() => router.push('/')}
70+
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
71+
>
72+
Go to Dashboard
73+
</button>
74+
</div>
75+
</Layout>
76+
);
77+
}
78+
79+
if (loading || !room) {
80+
return (
81+
<Layout>
82+
<div className="flex items-center justify-center min-h-screen">
83+
<LoadingSpinner size="lg" />
84+
</div>
85+
</Layout>
86+
);
87+
}
1488

1589
return (
16-
<div className="min-h-screen flex items-center justify-center theme-bg-primary">
17-
<LoadingSpinner size="lg" />
18-
</div>
90+
<Layout>
91+
<div className="h-[calc(100vh-64px)] flex flex-col">
92+
<ChatRoomContainer
93+
room={room}
94+
onBack={() => router.push('/')}
95+
/>
96+
</div>
97+
</Layout>
1998
);
2099
}

frontend/pages/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,9 @@ export default function Home() {
636636
const isRightSwipe = distance < -minSwipeDistance;
637637

638638
if (isLeftSwipe) {
639-
// Swipe Left (Move Right in flow): Sidebar -> Content -> Profile
639+
// Swipe Left (Move Right in flow): Sidebar -> Content
640640
if (mobileView === 'sidebar') {
641641
setMobileView('content');
642-
} else if (mobileView === 'content') {
643-
router.push('/profile');
644642
}
645643
}
646644

frontend/pages/settings.tsx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useState, useEffect, useRef } from 'react';
22
import { useRouter } from 'next/router';
33
import { useAuth } from '@/contexts/AuthContext';
44
import { useLanguage } from '@/contexts/LanguageContext';
@@ -52,6 +52,44 @@ const Settings: React.FC = () => {
5252
const [loadingIdentities, setLoadingIdentities] = useState(false);
5353
const [deletingIdentityId, setDeletingIdentityId] = useState<string | null>(null);
5454

55+
// Swipe handlers
56+
const touchStart = useRef<number | null>(null);
57+
const touchEnd = useRef<number | null>(null);
58+
const minSwipeDistance = 50;
59+
60+
const onTouchStart = (e: React.TouchEvent) => {
61+
touchEnd.current = null;
62+
touchStart.current = e.targetTouches[0].clientX;
63+
};
64+
65+
const onTouchMove = (e: React.TouchEvent) => {
66+
touchEnd.current = e.targetTouches[0].clientX;
67+
};
68+
69+
const onTouchEnd = () => {
70+
if (!touchStart.current || !touchEnd.current) return;
71+
const distance = touchStart.current - touchEnd.current;
72+
const isLeftSwipe = distance > minSwipeDistance;
73+
const isRightSwipe = distance < -minSwipeDistance;
74+
75+
const tabs: ('preferences' | 'account' | 'privacy' | 'anonymous-identities')[] = ['preferences', 'account', 'privacy', 'anonymous-identities'];
76+
const currentIndex = tabs.indexOf(activeTab);
77+
78+
if (isLeftSwipe) {
79+
// Swipe Left -> Move to Next Tab
80+
if (currentIndex < tabs.length - 1) {
81+
updateTab(tabs[currentIndex + 1]);
82+
}
83+
}
84+
85+
if (isRightSwipe) {
86+
// Swipe Right -> Move to Prev Tab
87+
if (currentIndex > 0) {
88+
updateTab(tabs[currentIndex - 1]);
89+
}
90+
}
91+
};
92+
5593
// Listen for tour events
5694
useEffect(() => {
5795
const handleSwitchTab = (e: CustomEvent) => {
@@ -295,7 +333,12 @@ const Settings: React.FC = () => {
295333

296334
return (
297335
<Layout>
298-
<div className="max-w-4xl mx-auto py-8 px-4">
336+
<div
337+
className="max-w-4xl mx-auto py-8 px-4"
338+
onTouchStart={onTouchStart}
339+
onTouchMove={onTouchMove}
340+
onTouchEnd={onTouchEnd}
341+
>
299342
<div className="mb-8">
300343
<button
301344
id="back-to-dashboard-btn"

0 commit comments

Comments
 (0)