From 772b8507585ad2f300bb799c9092ebd17217d868 Mon Sep 17 00:00:00 2001 From: Peanut Date: Tue, 17 Mar 2026 13:29:23 +0700 Subject: [PATCH 01/31] chore: remove chat/socket features --- client/src/components/ConnectionStatus.tsx | 63 ---------------------- 1 file changed, 63 deletions(-) delete mode 100644 client/src/components/ConnectionStatus.tsx diff --git a/client/src/components/ConnectionStatus.tsx b/client/src/components/ConnectionStatus.tsx deleted file mode 100644 index cc99550..0000000 --- a/client/src/components/ConnectionStatus.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { Wifi, WifiOff, AlertCircle } from 'lucide-react'; -import { useSocket } from '@/providers/SocketProvider'; -import { Alert, AlertDescription } from '@/components/ui/alert'; - -export function ConnectionStatus() { - const { connected, connectionError } = useSocket(); - - // Don't show anything if connected and no errors - if (connected && !connectionError) { - return null; - } - - return ( -
- {connectionError ? ( - - - - Connection Error: {connectionError} - - - ) : !connected ? ( - - - - Connecting to chat server... - - - ) : null} -
- ); -} - -// Inline connection indicator for chat components -export function InlineConnectionStatus() { - const { connected, connectionError } = useSocket(); - - if (connected && !connectionError) { - return ( -
- - Connected -
- ); - } - - if (connectionError) { - return ( -
- - Connection Error -
- ); - } - - return ( -
- - Connecting... -
- ); -} From 248da8a54f96cd1ab61b3b92573f6933d6dbf2f3 Mon Sep 17 00:00:00 2001 From: Peanut Date: Tue, 17 Mar 2026 13:29:24 +0700 Subject: [PATCH 02/31] chore: remove chat/socket features --- client/src/routes/room.$id.tsx | 59 ---------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 client/src/routes/room.$id.tsx diff --git a/client/src/routes/room.$id.tsx b/client/src/routes/room.$id.tsx deleted file mode 100644 index 0c439bf..0000000 --- a/client/src/routes/room.$id.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { createFileRoute, useParams, Link, redirect } from '@tanstack/react-router' -import { Button } from '@/components/ui/button' -import { ArrowLeft } from 'lucide-react' -import { ChatRoom } from '@/components/chat/ChatRoom' -import { useAuthStore } from '@/stores/authStore' - - -export const Route = createFileRoute('/room/$id')({ - beforeLoad: ({ location }) => { - // Check if user is authenticated - const isAuthenticated = useAuthStore.getState().isAuthenticated - - if (!isAuthenticated) { - throw redirect({ - to: '/login', - search: { - redirect: location.href, - }, - }) - } - }, - component: RoomPage, -}) - -function RoomPage() { - const { id } = useParams({ from: '/room/$id' }) - - // Socket connection is now managed by SocketProvider - - return ( -
- {/* Header */} -
-
-
- - - -
-
- - - -
-
-
- - {/* Chat Room */} -
- -
-
- ) -} From 56648d9c6ac62531ee93f6944d6a8f9389f0abb4 Mon Sep 17 00:00:00 2001 From: Peanut Date: Tue, 17 Mar 2026 13:29:25 +0700 Subject: [PATCH 03/31] chore: remove chat/socket features --- client/src/services/chat.service.ts | 188 ---------------------------- 1 file changed, 188 deletions(-) delete mode 100644 client/src/services/chat.service.ts diff --git a/client/src/services/chat.service.ts b/client/src/services/chat.service.ts deleted file mode 100644 index 4bca502..0000000 --- a/client/src/services/chat.service.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { httpClient } from '@/lib/http-client'; -import { MessageData, RoomData } from './socket.service'; - -export interface CreateRoomRequest { - name: string; - avatarUrl?: string; -} - -export interface UpdateRoomRequest { - name?: string; - avatarUrl?: string; -} - -export interface CreateMessageRequest { - content: string; - roomId: string; -} - -export interface UpdateMessageRequest { - content: string; -} - -export interface PaginatedResponse { - data: T[]; - total: number; - page: number; - limit: number; - totalPages: number; -} - -export interface RoomsResponse { - rooms: RoomData[]; - total: number; - page: number; - limit: number; - totalPages: number; -} - -export interface MessagesResponse { - messages: MessageData[]; - total: number; - page: number; - limit: number; - totalPages: number; -} - -export interface InviteUsersResponse { - invitedUsers: { - id: string; - username: string; - email: string; - }[]; - alreadyMembers: string[]; - notFound: string[]; -} - -export interface RoomMember { - id: string; - username: string; - email: string; - avatarUrl: string; - isOnline: boolean; - isAuthor: boolean; - joinedAt: string; -} - -export interface RoomMembersResponse { - members: RoomMember[]; - totalMembers: number; - roomInfo: { - id: string; - name: string; - authorId: string; - createdAt: string; - }; -} - -class ChatService { - private baseUrl = '/chat'; - - // Room operations - async getRooms( - page = 1, - limit = 10, - sortBy: 'name' | 'updated_at' | 'created_at' = 'updated_at', - sortOrder: 'asc' | 'desc' = 'desc' - ): Promise { - const params = new URLSearchParams({ - page: page.toString(), - limit: limit.toString(), - sortBy, - sortOrder - }); - - const response = await httpClient.get( - `${this.baseUrl}/rooms?${params.toString()}` - ); - // HttpClient returns ApiResponse, so we need response.data - return response.data || { rooms: [], total: 0, page: 1, limit: 10, totalPages: 0 }; - } - - async getRoom(roomId: string): Promise { - const response = await httpClient.get<{ room: RoomData }>( - `${this.baseUrl}/rooms/${roomId}` - ); - // HttpClient returns ApiResponse<{ room: RoomData }>, so we need response.data?.room - return response.data?.room || {} as RoomData; - } - - async createRoom(data: CreateRoomRequest): Promise { - const response = await httpClient.post<{ room: RoomData }>( - `${this.baseUrl}/rooms`, - data - ); - // HttpClient returns ApiResponse<{ room: RoomData }>, so we need response.data?.room - return response.data?.room || {} as RoomData; - } - - async updateRoom(roomId: string, data: UpdateRoomRequest): Promise { - const response = await httpClient.put<{ room: RoomData }>( - `${this.baseUrl}/rooms/${roomId}`, - data - ); - // HttpClient returns ApiResponse<{ room: RoomData }>, so we need response.data?.room - return response.data?.room || {} as RoomData; - } - - async deleteRoom(roomId: string): Promise { - await httpClient.delete(`${this.baseUrl}/rooms/${roomId}`); - } - - async joinRoom(roomId: string): Promise { - const response = await httpClient.post<{ room: RoomData }>( - `${this.baseUrl}/rooms/${roomId}/join` - ); - // HttpClient returns ApiResponse<{ room: RoomData }>, so we need response.data?.room - return response.data?.room || {} as RoomData; - } - - async leaveRoom(roomId: string): Promise { - await httpClient.post(`${this.baseUrl}/rooms/${roomId}/leave`); - } - - async inviteUsers(roomId: string, userIds: string[]): Promise { - const response = await httpClient.post( - `${this.baseUrl}/rooms/${roomId}/invite`, - { userIds } - ); - // HttpClient returns ApiResponse, so we need response.data - return response.data || { invitedUsers: [], alreadyMembers: [], notFound: [] }; - } - - async getRoomMembers(roomId: string): Promise { - const response = await httpClient.get( - `${this.baseUrl}/rooms/${roomId}/members` - ); - // HttpClient returns ApiResponse, so we need response.data - return response.data || { members: [], totalMembers: 0, roomInfo: { id: '', name: '', authorId: '', createdAt: '' } }; - } - - async removeMember(roomId: string, memberId: string): Promise { - await httpClient.delete(`${this.baseUrl}/rooms/${roomId}/members/${memberId}`); - } - - // Message operations - async getMessages(roomId: string, page = 1, limit = 50): Promise { - const response = await httpClient.get( - `${this.baseUrl}/rooms/${roomId}/messages?page=${page}&limit=${limit}` - ); - // HttpClient returns ApiResponse, so we need response.data - return response.data || { messages: [], total: 0, page: 1, limit: 50, totalPages: 0 }; - } - - async updateMessage(roomId: string, messageId: string, data: UpdateMessageRequest): Promise { - const response = await httpClient.put<{ message: MessageData }>( - `${this.baseUrl}/rooms/${roomId}/messages/${messageId}`, - data - ); - // HttpClient returns ApiResponse<{ message: MessageData }>, so we need response.data?.message - return response.data?.message || {} as MessageData; - } - - async deleteMessage(roomId: string, messageId: string): Promise { - await httpClient.delete(`${this.baseUrl}/rooms/${roomId}/messages/${messageId}`); - } -} - -export const chatService = new ChatService(); From 271428ebbdd429842c2373025448bf047e3ad7b3 Mon Sep 17 00:00:00 2001 From: Peanut Date: Tue, 17 Mar 2026 13:29:26 +0700 Subject: [PATCH 04/31] chore: remove chat/socket features --- client/src/hooks/useChat.ts | 537 ------------------------------------ 1 file changed, 537 deletions(-) delete mode 100644 client/src/hooks/useChat.ts diff --git a/client/src/hooks/useChat.ts b/client/src/hooks/useChat.ts deleted file mode 100644 index d21df15..0000000 --- a/client/src/hooks/useChat.ts +++ /dev/null @@ -1,537 +0,0 @@ -import { useQuery, useMutation, useQueryClient, useInfiniteQuery } from '@tanstack/react-query'; -import { useNavigate } from '@tanstack/react-router'; -import { chatService, CreateRoomRequest, UpdateRoomRequest, UpdateMessageRequest } from '@/services/chat.service'; -import { socketService, MessageData, TypingData } from '@/services/socket.service'; -import { useChatStore } from '@/stores/chatStore'; -import { useEffect, useCallback, useState } from 'react'; - -// Query keys -export const chatKeys = { - all: ['chat'] as const, - rooms: () => [...chatKeys.all, 'rooms'] as const, - room: (id: string) => [...chatKeys.all, 'room', id] as const, - roomMembers: (roomId: string) => [...chatKeys.all, 'room', roomId, 'members'] as const, - messages: (roomId: string) => [...chatKeys.all, 'messages', roomId] as const, -}; - -// Rooms hooks -export function useRooms( - page = 1, - limit = 10, - sortBy: 'name' | 'updated_at' | 'created_at' = 'updated_at', - sortOrder: 'asc' | 'desc' = 'desc' -) { - return useQuery({ - queryKey: [...chatKeys.rooms(), page, limit, sortBy, sortOrder], - queryFn: () => chatService.getRooms(page, limit, sortBy, sortOrder), - staleTime: 5 * 60 * 1000, // 5 minutes - }); -} - -export function useRoom(roomId: string) { - return useQuery({ - queryKey: chatKeys.room(roomId), - queryFn: () => chatService.getRoom(roomId), - enabled: !!roomId, - staleTime: 5 * 60 * 1000, - }); -} - -export function useCreateRoom() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data: CreateRoomRequest) => chatService.createRoom(data), - onSuccess: () => { - // Invalidate rooms list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - }, - }); -} - -export function useUpdateRoom(roomId: string) { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data: UpdateRoomRequest) => chatService.updateRoom(roomId, data), - onSuccess: (updatedRoom) => { - // Update room cache - queryClient.setQueryData(chatKeys.room(roomId), updatedRoom); - // Invalidate rooms list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - }, - }); -} - -export function useDeleteRoom() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (roomId: string) => chatService.deleteRoom(roomId), - onSuccess: (_, roomId) => { - // Remove room from cache - queryClient.removeQueries({ queryKey: chatKeys.room(roomId) }); - // Invalidate rooms list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - }, - }); -} - -export function useJoinRoom() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (roomId: string) => chatService.joinRoom(roomId), - onSuccess: (updatedRoom, roomId) => { - // Update room cache - queryClient.setQueryData(chatKeys.room(roomId), updatedRoom); - // Invalidate rooms list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - - // Join room via socket - socketService.joinRoom(roomId); - }, - }); -} - -export function useLeaveRoom() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (roomId: string) => chatService.leaveRoom(roomId), - onSuccess: (_, roomId) => { - // Leave room via socket - socketService.leaveRoom(roomId); - // Invalidate rooms list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - }, - }); -} - -export function useInviteUsers() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ roomId, userIds }: { roomId: string; userIds: string[] }) => - chatService.inviteUsers(roomId, userIds), - onSuccess: (data, variables) => { - // Invalidate rooms list and specific room to refresh participant count - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - queryClient.invalidateQueries({ queryKey: chatKeys.room(variables.roomId) }); - - // Show success message - console.log(`Successfully invited ${data.invitedUsers.length} users`); - if (data.alreadyMembers.length > 0) { - console.log(`${data.alreadyMembers.length} users were already members`); - } - if (data.notFound.length > 0) { - console.log(`${data.notFound.length} users were not found`); - } - }, - }); -} - -export function useRoomMembers(roomId: string) { - return useQuery({ - queryKey: chatKeys.roomMembers(roomId), - queryFn: () => chatService.getRoomMembers(roomId), - enabled: !!roomId, - staleTime: 2 * 60 * 1000, // 2 minutes - }); -} - -export function useRemoveMember() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ roomId, memberId }: { roomId: string; memberId: string }) => - chatService.removeMember(roomId, memberId), - onSuccess: (_, variables) => { - // Invalidate room members to refresh the list - queryClient.invalidateQueries({ queryKey: chatKeys.roomMembers(variables.roomId) }); - // Also invalidate room data to update participant count - queryClient.invalidateQueries({ queryKey: chatKeys.room(variables.roomId) }); - }, - }); -} - -// Messages hooks -export function useMessages(roomId: string, page = 1, limit = 50) { - return useQuery({ - queryKey: [...chatKeys.messages(roomId), page, limit], - queryFn: () => chatService.getMessages(roomId, page, limit), - enabled: !!roomId, - staleTime: 1 * 60 * 1000, // 1 minute - }); -} - -export function useInfiniteMessages(roomId: string, limit = 50) { - return useInfiniteQuery({ - queryKey: [...chatKeys.messages(roomId), 'infinite'], - queryFn: ({ pageParam = 1 }) => chatService.getMessages(roomId, pageParam as number, limit), - enabled: !!roomId, - initialPageParam: 1, - getNextPageParam: (lastPage: any) => { - if (lastPage.page < lastPage.totalPages) { - return lastPage.page + 1; - } - return undefined; - }, - staleTime: 1 * 60 * 1000, - }); -} - -export function useUpdateMessage(roomId: string) { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ messageId, data }: { messageId: string; data: UpdateMessageRequest }) => - chatService.updateMessage(roomId, messageId, data), - onSuccess: () => { - // Invalidate messages for this room - queryClient.invalidateQueries({ queryKey: chatKeys.messages(roomId) }); - }, - }); -} - -export function useDeleteMessage(roomId: string) { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (messageId: string) => chatService.deleteMessage(roomId, messageId), - onSuccess: () => { - // Invalidate messages for this room - queryClient.invalidateQueries({ queryKey: chatKeys.messages(roomId) }); - }, - }); -} - -// Real-time message hook -export function useRealTimeMessages(roomId: string) { - const queryClient = useQueryClient(); - - const addMessage = useCallback((message: MessageData) => { - if (message.roomId !== roomId) return; - - // Add message to infinite query cache - queryClient.setQueryData( - [...chatKeys.messages(roomId), 'infinite'], - (oldData: any) => { - if (!oldData) return oldData; - - const newPages = [...oldData.pages]; - if (newPages.length > 0) { - // Add to first page (newest messages at the end) - newPages[0] = { - ...newPages[0], - messages: [...newPages[0].messages, message], // Add at the end for ASC sorting - total: newPages[0].total + 1, - }; - } - - return { - ...oldData, - pages: newPages, - }; - } - ); - - // Also invalidate regular messages query - queryClient.invalidateQueries({ queryKey: chatKeys.messages(roomId) }); - }, [queryClient, roomId]); - - const updateMessage = useCallback((data: { messageId: string; content: string; roomId: string }) => { - if (data.roomId !== roomId) return; - - // Update message in cache - queryClient.setQueryData( - [...chatKeys.messages(roomId), 'infinite'], - (oldData: any) => { - if (!oldData) return oldData; - - const newPages = oldData.pages.map((page: any) => ({ - ...page, - messages: page.messages.map((msg: MessageData) => - msg.id === data.messageId - ? { ...msg, content: data.content, updatedAt: new Date().toISOString() } - : msg - ), - })); - - return { - ...oldData, - pages: newPages, - }; - } - ); - }, [queryClient, roomId]); - - const deleteMessage = useCallback((data: { messageId: string; roomId: string }) => { - if (data.roomId !== roomId) return; - - // Remove message from cache - queryClient.setQueryData( - [...chatKeys.messages(roomId), 'infinite'], - (oldData: any) => { - if (!oldData) return oldData; - - const newPages = oldData.pages.map((page: any) => ({ - ...page, - messages: page.messages.filter((msg: MessageData) => msg.id !== data.messageId), - total: Math.max(0, page.total - 1), - })); - - return { - ...oldData, - pages: newPages, - }; - } - ); - }, [queryClient, roomId]); - - useEffect(() => { - if (!roomId) return; - - // Subscribe to real-time events - const unsubscribeNewMessage = socketService.onNewMessage(addMessage); - const unsubscribeUpdateMessage = socketService.onMessageUpdate(updateMessage); - const unsubscribeDeleteMessage = socketService.onMessageDelete(deleteMessage); - - return () => { - unsubscribeNewMessage(); - unsubscribeUpdateMessage(); - unsubscribeDeleteMessage(); - }; - }, [roomId, addMessage, updateMessage, deleteMessage]); -} - -// Real-time typing indicators hook -export function useRealTimeTyping(roomId: string) { - const { addTypingUser, removeTypingUser } = useChatStore(); - - const handleTyping = useCallback((data: TypingData) => { - if (data.roomId !== roomId) return; - - if (data.isTyping) { - addTypingUser({ - userId: data.userId, - username: data.username, - roomId: data.roomId, - }); - } else { - removeTypingUser(data.userId, data.roomId); - } - }, [roomId, addTypingUser, removeTypingUser]); - - useEffect(() => { - const unsubscribe = socketService.onTyping(handleTyping); - return unsubscribe; - }, [handleTyping]); -} - -// Real-time user offline status hook -export function useRealTimeUserStatus() { - const queryClient = useQueryClient(); - - const handleUserOfflineInRoom = useCallback((data: { userId: string; username: string; roomId: string }) => { - // Invalidate room members query to refresh online status - queryClient.invalidateQueries({ queryKey: chatKeys.roomMembers(data.roomId) }); - - // Optionally show a notification that user went offline - console.log(`${data.username} went offline in room ${data.roomId}`); - }, [queryClient]); - - useEffect(() => { - const unsubscribe = socketService.onUserOfflineInRoom(handleUserOfflineInRoom); - return unsubscribe; - }, [handleUserOfflineInRoom]); -} - -// Real-time room deletion hook -export function useRealTimeRoomDeletion() { - const queryClient = useQueryClient(); - - const handleRoomDeleted = useCallback((data: { roomId: string; roomName: string; message: string }) => { - // Invalidate rooms query to refresh the room list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - - // Show notification about room deletion - console.log(`Room deleted: ${data.message}`); - - // If user is currently in the deleted room, they should be redirected - // This will be handled by the individual room components - }, [queryClient]); - - useEffect(() => { - const unsubscribe = socketService.onRoomDeleted(handleRoomDeleted); - return unsubscribe; - }, [handleRoomDeleted]); -} - -// Hook for handling room deletion when user is in the specific room -export function useRoomDeletionRedirect(roomId: string) { - const navigate = useNavigate(); - - const handleRoomDeleted = useCallback((data: { roomId: string; roomName: string; message: string }) => { - // Only handle if this is the current room - if (data.roomId === roomId) { - // Show alert/notification - alert(data.message); - - // Redirect to chat list - navigate({ to: '/chat' }); - } - }, [roomId, navigate]); - - useEffect(() => { - const unsubscribe = socketService.onRoomDeleted(handleRoomDeleted); - return unsubscribe; - }, [handleRoomDeleted]); -} - -// Real-time room update hook -export function useRealTimeRoomUpdates() { - const queryClient = useQueryClient(); - - const handleRoomUpdated = useCallback((data: { roomId: string; roomName: string; avatarUrl: string; updatedRoom: any; message: string }) => { - // Invalidate specific room query - queryClient.invalidateQueries({ queryKey: chatKeys.room(data.roomId) }); - - // Show notification about room update - console.log(`Room updated: ${data.message}`); - }, [queryClient]); - - const handleRoomListUpdated = useCallback((data: { action: string; room: any }) => { - // Invalidate rooms query to refresh the room list - queryClient.invalidateQueries({ queryKey: chatKeys.rooms() }); - - console.log(`Room list updated: ${data.action} room ${data.room?.name || ''}`); - }, [queryClient]); - - useEffect(() => { - const unsubscribeRoomUpdated = socketService.onRoomUpdated(handleRoomUpdated); - const unsubscribeRoomListUpdated = socketService.onRoomListUpdated(handleRoomListUpdated); - - return () => { - unsubscribeRoomUpdated(); - unsubscribeRoomListUpdated(); - }; - }, [handleRoomUpdated, handleRoomListUpdated]); -} - -// Hook for handling room updates when user is in the specific room -export function useRoomUpdateNotification(roomId: string) { - const handleRoomUpdated = useCallback((data: { roomId: string; roomName: string; avatarUrl: string; updatedRoom: any; message: string }) => { - // Only handle if this is the current room - if (data.roomId === roomId) { - // Show notification about room update - console.log(`Current room updated: ${data.message}`); - // You could show a toast notification here instead of console.log - } - }, [roomId]); - - useEffect(() => { - const unsubscribe = socketService.onRoomUpdated(handleRoomUpdated); - return unsubscribe; - }, [handleRoomUpdated]); -} - -// Hook for handling user removal from room (redirect user) -export function useUserRemovalRedirect() { - const navigate = useNavigate(); - - const handleUserRemovedFromRoom = useCallback((data: { roomId: string; roomName: string; message: string }) => { - // Show alert/notification - alert(data.message); - - // Redirect to chat list - navigate({ to: '/chat' }); - }, [navigate]); - - useEffect(() => { - const unsubscribe = socketService.onUserRemovedFromRoom(handleUserRemovedFromRoom); - return unsubscribe; - }, [handleUserRemovedFromRoom]); -} - -// Hook for handling member removal notifications in room -export function useRealTimeMemberRemoval() { - const queryClient = useQueryClient(); - - const handleMemberRemoved = useCallback((data: { roomId: string; roomName: string; removedUserId: string; removedUsername: string; message: string }) => { - // Invalidate room members query to refresh the member list - queryClient.invalidateQueries({ queryKey: chatKeys.roomMembers(data.roomId) }); - - // Invalidate room data to update participant count - queryClient.invalidateQueries({ queryKey: chatKeys.room(data.roomId) }); - - // Show notification about member removal - console.log(`Member removed: ${data.message}`); - }, [queryClient]); - - useEffect(() => { - const unsubscribe = socketService.onMemberRemoved(handleMemberRemoved); - return unsubscribe; - }, [handleMemberRemoved]); -} - -// Socket connection hook with proper lifecycle management -export function useSocketConnection() { - const queryClient = useQueryClient(); - const [connectionState, setConnectionState] = useState({ - connected: socketService.connected, - socketId: socketService.socketId, - }); - - useEffect(() => { - const connectSocket = async () => { - try { - if (!socketService.connected) { - console.log('Connecting to socket...'); - await socketService.connect(); - } - } catch (error) { - console.error('Failed to connect to socket:', error); - } - }; - - connectSocket(); - - // Cleanup on unmount - this will decrement reference count - return () => { - console.log('Disconnecting socket (component unmount)'); - socketService.disconnect(); - }; - }, []); // Empty dependency array - only run once per component mount - - useEffect(() => { - const unsubscribe = socketService.onConnectionChange((connected) => { - console.log('Socket connection state changed:', connected); - setConnectionState({ - connected, - socketId: socketService.socketId, - }); - - if (connected) { - // Invalidate all chat queries when reconnected - queryClient.invalidateQueries({ queryKey: chatKeys.all }); - } - }); - - return unsubscribe; - }, [queryClient]); - - return connectionState; -} - -// Lightweight hook for components that just need to know connection status -export function useSocketStatus() { - const [connected, setConnected] = useState(socketService.connected); - - useEffect(() => { - const unsubscribe = socketService.onConnectionChange(setConnected); - return unsubscribe; - }, []); - - return { connected }; -} From dddb233d72fdd3b7859972211ca8870cd11c9fc4 Mon Sep 17 00:00:00 2001 From: Peanut Date: Tue, 17 Mar 2026 13:29:27 +0700 Subject: [PATCH 05/31] chore: remove chat/socket features --- client/src/routes/verify-otp.tsx | 84 -------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 client/src/routes/verify-otp.tsx diff --git a/client/src/routes/verify-otp.tsx b/client/src/routes/verify-otp.tsx deleted file mode 100644 index a39b159..0000000 --- a/client/src/routes/verify-otp.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { createFileRoute, Link, useSearch } from '@tanstack/react-router' -import React, { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { ArrowLeft } from 'lucide-react' - -export const Route = createFileRoute('/verify-otp')({ - component: VerifyOtpPage, - validateSearch: (search: Record) => ({ - e: (search.e as string) || '', - }), -}) - -function VerifyOtpPage() { - const search = useSearch({ from: '/verify-otp' }) - const [otp, setOtp] = useState("") - - const handleSubmit = () => { - console.log('Verifying OTP:', otp, 'for email:', search.e) - // Handle OTP verification logic here - } - - return ( -
- {/* Back to Forgot Password Button */} - - - - - - - Verify OTP - - Enter the 6-digit code sent to {search.e || 'your email'} - - - -
- - setOtp(e.target.value)} - maxLength={6} - className="text-center text-lg tracking-widest" - required - /> -
- -
- - Resend Code - -
- - Back to Sign In - -
-
-
-
-
- ) -} From 47336f298ed7530601facf93595d0afe622fad5b Mon Sep 17 00:00:00 2001 From: Peanut Date: Tue, 17 Mar 2026 13:29:28 +0700 Subject: [PATCH 06/31] chore: remove chat/socket features --- client/src/components/chat/MessageInput.tsx | 194 -------------------- 1 file changed, 194 deletions(-) delete mode 100644 client/src/components/chat/MessageInput.tsx diff --git a/client/src/components/chat/MessageInput.tsx b/client/src/components/chat/MessageInput.tsx deleted file mode 100644 index 032fe85..0000000 --- a/client/src/components/chat/MessageInput.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { useState, useRef, useEffect, useCallback } from "react"; -import { Send } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useChatStore } from "@/stores/chatStore"; -import { EmojiPicker } from "./EmojiPicker"; - -interface MessageInputProps { - roomId: string; - disabled?: boolean; - placeholder?: string; -} - -export function MessageInput({ - roomId, - disabled = false, - placeholder = "Type a message...", -}: MessageInputProps) { - const [message, setMessage] = useState(""); - const [isTyping, setIsTyping] = useState(false); - const textareaRef = useRef(null); - const typingTimeoutRef = useRef(null); - const lastTypingTimeRef = useRef(0); - - const { sendMessage, setTyping } = useChatStore(); - - const handleEmojiSelect = (emoji: string) => { - const textarea = textareaRef.current; - if (!textarea) return; - - const start = textarea.selectionStart; - const end = textarea.selectionEnd; - const newMessage = message.slice(0, start) + emoji + message.slice(end); - - setMessage(newMessage); - - // Trigger input change logic for typing indicator - handleInputChange({ - target: { value: newMessage }, - } as React.ChangeEvent); - - // Focus back to textarea and set cursor position - setTimeout(() => { - textarea.focus(); - const newCursorPos = start + emoji.length; - textarea.setSelectionRange(newCursorPos, newCursorPos); - }, 0); - }; - - const handleSubmit = useCallback( - (e: React.FormEvent) => { - e.preventDefault(); - - if (!message.trim() || disabled) return; - - // Send message - sendMessage(roomId, message.trim()); - - // Clear input - setMessage(""); - - // Stop typing indicator - if (isTyping) { - setTyping(roomId, false); - setIsTyping(false); - } - - // Reset textarea height - if (textareaRef.current) { - textareaRef.current.style.height = "auto"; - } - }, - [message, disabled, roomId, sendMessage, setTyping] - ); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - e.preventDefault(); - handleSubmit(e); - } - }, - [handleSubmit] - ); - - const handleInputChange = useCallback( - (e: React.ChangeEvent) => { - const value = e.target.value; - setMessage(value); - - // Auto-resize textarea - if (textareaRef.current) { - textareaRef.current.style.height = "auto"; - textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`; - } - - // Handle typing indicator with debounce - const now = Date.now(); - - if (value.trim()) { - // Start typing indicator if not already typing - if (!isTyping) { - setIsTyping(true); - setTyping(roomId, true); - lastTypingTimeRef.current = now; - } else { - // Debounce: only send typing update if it's been more than 1 second since last update - if (now - lastTypingTimeRef.current > 1000) { - setTyping(roomId, true); - lastTypingTimeRef.current = now; - } - } - - // Clear existing timeout - if (typingTimeoutRef.current) { - clearTimeout(typingTimeoutRef.current); - } - - // Set new timeout to stop typing after user stops typing - typingTimeoutRef.current = window.setTimeout(() => { - setIsTyping(false); - setTyping(roomId, false); - }, 3000); // ✅ Stop typing after 3 seconds of inactivity - } else if (isTyping) { - // Stop typing immediately if input is empty - setIsTyping(false); - setTyping(roomId, false); - if (typingTimeoutRef.current) { - clearTimeout(typingTimeoutRef.current); - typingTimeoutRef.current = null; - } - } - }, - [isTyping, roomId, setTyping] - ); - - // Cleanup on unmount - useEffect(() => { - return () => { - if (typingTimeoutRef.current) { - clearTimeout(typingTimeoutRef.current); - } - if (isTyping) { - setTyping(roomId, false); - } - }; - }, [roomId, isTyping, setTyping]); - - - return ( -
-
-
-