Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions app/logout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,57 @@
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuthToken } from "@/hooks/use-auth-token";
import { socketService } from "@/services/socketService";
import { apiSlice } from "@/states/apiSlice";
import { useDispatch } from "react-redux";

const LogoutPage = () => {
const router = useRouter();
const { removeToken } = useAuthToken();
const dispatch = useDispatch();

useEffect(() => {
removeToken();
router.replace("/");
}, [router, removeToken]);
const performLogout = () => {
try {
// 1. Clear authentication tokens
removeToken();

// 2. Force disconnect socket (this will also clear chat state)
socketService.forceDisconnect();

// 3. Clear Redux RTK Query cache
dispatch(apiSlice.util.resetApiState());

// 4. Clear sessionStorage (transfer data, etc.)
sessionStorage.clear();

// 5. Clear specific localStorage items while preserving theme and sidebar preferences
const preservedItems = {
theme: localStorage.getItem('theme'),
sidebarExpanded: localStorage.getItem('sidebarExpanded'),
};

// Clear all localStorage
localStorage.clear();

// Restore preserved items
if (preservedItems.theme) {
localStorage.setItem('theme', preservedItems.theme);
}
if (preservedItems.sidebarExpanded) {
localStorage.setItem('sidebarExpanded', preservedItems.sidebarExpanded);
}
} catch (error) {
console.error('Error during logout:', error);
}

// 6. Immediate redirect using window.location for guaranteed navigation
window.location.href = "/";
};

performLogout();
}, []); // Empty deps - only run once on mount

return null;
};

Expand Down
56 changes: 54 additions & 2 deletions components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import NotificationBell from "./notifications/NotificationBell";
import { useAuthToken } from "@/hooks/use-auth-token";
import { Button } from "./ui/button";
import { useTheme } from "@/context/ThemeContext";
import { socketService } from "@/services/socketService";
import { apiSlice } from "@/states/apiSlice";
import { useDispatch } from "react-redux";


export const Header = () => {
Expand All @@ -26,8 +29,9 @@ export const Header = () => {

const router = useRouter();
const notifications = useNotifications();
const { getToken } = useAuthToken();
const { getToken, removeToken } = useAuthToken();
const { theme, toggleTheme } = useTheme();
const dispatch = useDispatch();

// Try to get chat context, but don't fail if it's not available
let chat;
Expand Down Expand Up @@ -130,6 +134,54 @@ export const Header = () => {
setIsBalanceVisible(!isBalanceVisible);
};

const handleLogout = () => {
setIsDropdownOpen(false);

try {
// 1. Clear authentication tokens
removeToken();

// 2. Clear chat state if available
if (chat?.clearChatState) {
chat.clearChatState();
}

// 3. Clear notification state if available
if (notifications?.clearNotificationState) {
notifications.clearNotificationState();
}

// 4. Force disconnect socket
socketService.forceDisconnect();

// 5. Clear Redux RTK Query cache
dispatch(apiSlice.util.resetApiState());

// 6. Clear sessionStorage
sessionStorage.clear();

// 7. Clear localStorage (preserve theme and sidebar)
const preservedItems = {
theme: localStorage.getItem('theme'),
sidebarExpanded: localStorage.getItem('sidebarExpanded'),
};

localStorage.clear();

if (preservedItems.theme) {
localStorage.setItem('theme', preservedItems.theme);
}
if (preservedItems.sidebarExpanded) {
localStorage.setItem('sidebarExpanded', preservedItems.sidebarExpanded);
}
} catch (error) {
console.error('Error during logout:', error);
}

// 8. Force hard redirect to home (bypasses middleware returnUrl)
window.location.replace("/");
};

const handleNavigation = (path: string) => {
setIsDropdownOpen(false);
router.push(path);
Expand Down Expand Up @@ -244,7 +296,7 @@ export const Header = () => {
</li>
<li>
<button
onClick={() => handleNavigation('/logout')}
onClick={handleLogout}
className="block w-full text-left px-4 py-2 hover:bg-gray-100 dark:hover:bg-darkBg-interactive text-red-500"
>
Logout
Expand Down
49 changes: 43 additions & 6 deletions components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import {
import { cn } from "@/lib/utils"
import { useAuthToken } from "@/hooks/use-auth-token"
import { useSidebar } from "@/context/SidebarContext"
import { socketService } from "@/services/socketService"
import { apiSlice } from "@/states/apiSlice"
import { useDispatch } from "react-redux"

interface NavigationItem {
id: string
Expand All @@ -52,6 +55,7 @@ export default function Navigation({ hideBottomNav = false }: NavigationProps) {
const router = useRouter()
const params = useParams()
const pathname = usePathname()
const dispatch = useDispatch()

// Get userId from URL params or token
useEffect(() => {
Expand Down Expand Up @@ -144,8 +148,41 @@ export default function Navigation({ hideBottomNav = false }: NavigationProps) {
const handleClick = (id: string, path: string) => {
// Handle logout separately
if (id === "Logout") {
removeToken();
router.push('/auth/login');
try {
// 1. Clear authentication tokens
removeToken();

// 2. Force disconnect socket
socketService.forceDisconnect();

// 3. Clear Redux RTK Query cache
dispatch(apiSlice.util.resetApiState());

// 4. Clear sessionStorage
sessionStorage.clear();

// 5. Clear localStorage (preserve theme and sidebar)
const preservedItems = {
theme: localStorage.getItem('theme'),
sidebarExpanded: localStorage.getItem('sidebarExpanded'),
};

localStorage.clear();

if (preservedItems.theme) {
localStorage.setItem('theme', preservedItems.theme);
}
if (preservedItems.sidebarExpanded) {
localStorage.setItem('sidebarExpanded', preservedItems.sidebarExpanded);
}
} catch (error) {
console.error('Error during logout:', error);
}

// 6. Force complete page reload to home using full URL
setTimeout(() => {
window.location.href = window.location.origin + "/";
}, 50);
return;
}

Expand Down Expand Up @@ -218,8 +255,8 @@ export default function Navigation({ hideBottomNav = false }: NavigationProps) {
className={cn(
"flex items-center px-3 py-2.5 transition-all rounded-xl duration-200",
isExpanded ? "justify-start" : "justify-center",
activeItem === item.id
? "bg-brand-green dark:bg-brand-gold text-white dark:text-[#00313A] shadow-lg"
activeItem === item.id
? "bg-brand-green dark:bg-brand-gold text-white dark:text-[#00313A] shadow-lg"
: "text-white hover:bg-[#004D5C] hover:shadow-md",
)}
>
Expand All @@ -239,8 +276,8 @@ export default function Navigation({ hideBottomNav = false }: NavigationProps) {
className={cn(
"flex items-center px-3 py-3.5 transition-all rounded-xl duration-200",
isExpanded ? "justify-start" : "justify-center",
activeItem === item.id
? "bg-brand-green dark:bg-brand-gold text-white dark:text-[#00313A] shadow-lg"
activeItem === item.id
? "bg-brand-green dark:bg-brand-gold text-white dark:text-[#00313A] shadow-lg"
: "text-white hover:bg-[#004D5C] hover:shadow-md",
)}
>
Expand Down
48 changes: 46 additions & 2 deletions context/ChatContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface ChatContextType {
updateMessageReadStatus: (chatId: string, messageId: string, readBy: any) => void;
refreshConversations: () => void;
refreshMessages: (chatId: string) => void;
clearChatState: () => void;
}

const ChatContext = createContext<ChatContextType | undefined>(undefined);
Expand All @@ -63,6 +64,19 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
useEffect(() => {
const currentToken = getToken();
const currentUserId = getUserId();

// If userId changes (different user logged in), clear all state
if (userId && currentUserId && userId !== currentUserId) {
console.log('Different user detected, clearing chat state');
setConversations([]);
setActiveChat(null);
setMessages({});
setTypingUsers([]);
setOnlineUsers([]);
setParticipantsStatus({});
socketService.forceDisconnect();
}

setToken(currentToken);
setUserId(currentUserId);

Expand All @@ -73,6 +87,19 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
const handleAuthTokenChange = (event: CustomEvent) => {
const newToken = getToken();
const newUserId = getUserId();

// If user changed, clear state
if (userId && newUserId && userId !== newUserId) {
console.log('User changed via token event, clearing chat state');
setConversations([]);
setActiveChat(null);
setMessages({});
setTypingUsers([]);
setOnlineUsers([]);
setParticipantsStatus({});
socketService.forceDisconnect();
}

setToken(newToken);
setUserId(newUserId);
};
Expand All @@ -82,7 +109,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
return () => {
window.removeEventListener('authTokenChanged', handleAuthTokenChange as EventListener);
};
}, [getToken, getUserId]);
}, [getToken, getUserId, userId]);

const { data: chatsData, refetch: refetchChats } = useGetUserChatsQuery(undefined, {
skip: !token
Expand Down Expand Up @@ -571,6 +598,22 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
}));
}, []);

const clearChatState = useCallback(() => {
// Disconnect socket properly
socketService.disconnect();

// Clear all state
setConversations([]);
setActiveChat(null);
setMessages({});
setTypingUsers([]);
setOnlineUsers([]);
setParticipantsStatus({});
setIsConnected(false);
setUserId(null);
setToken(null);
}, []);

const value: ChatContextType = {
isConnected,
conversations,
Expand All @@ -593,7 +636,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
leaveChat,
markMessageRead,
addMessage,
updateMessageReadStatus
updateMessageReadStatus,
clearChatState
};

return (
Expand Down
40 changes: 39 additions & 1 deletion context/NotificationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,47 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({ chil
const { getToken } = useAuthToken()
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const [authToken, setAuthToken] = useState<string | null>(null)
const [currentUserId, setCurrentUserId] = useState<string | null>(null)

// Monitor token changes to trigger reconnection
useEffect(() => {
const token = getToken()
const userId = token ? getUserIdFromToken(token) : null

// If userId changes (different user logged in), clear all notifications
if (currentUserId && userId && currentUserId !== userId) {
console.log('Different user detected, clearing notification state')
setNotifications([])
setUnreadCount(0)
setIsConnected(false)
}

setAuthToken(token)
setCurrentUserId(userId)

// Listen for token changes via custom event
const handleAuthTokenChange = (event: CustomEvent) => {
const newToken = getToken()
const newUserId = newToken ? getUserIdFromToken(newToken) : null

// If user changed, clear notifications
if (currentUserId && newUserId && currentUserId !== newUserId) {
console.log('User changed via token event, clearing notification state')
setNotifications([])
setUnreadCount(0)
setIsConnected(false)
}

setAuthToken(newToken)
setCurrentUserId(newUserId)
}

window.addEventListener('authTokenChanged', handleAuthTokenChange as EventListener)

return () => {
window.removeEventListener('authTokenChanged', handleAuthTokenChange as EventListener)
}
}, [getToken])
}, [getToken, currentUserId])

const addNotification = useCallback((notification: Notification) => {
setNotifications((prev) => {
Expand Down Expand Up @@ -80,6 +103,20 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({ chil
setUnreadCount(0)
}, [])

const clearNotificationState = useCallback(() => {
// Disconnect notification listeners
const socket = socketService.getSocket();
if (socket) {
socketService.offNotification();
}

// Clear all state
setNotifications([]);
setUnreadCount(0);
setIsConnected(false);
setAuthToken(null);
}, []);

const removeNotification = useCallback((notificationId: string) => {
setNotifications((prev) => {
const notification = prev.find(n => n.id === notificationId)
Expand Down Expand Up @@ -356,6 +393,7 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({ chil
removeNotification,
removeContactRequestNotification,
isConnected,
clearNotificationState,
}

return React.createElement(
Expand Down
Loading