From c66d447c0026e8c2e2d1354a7351bb412bf69744 Mon Sep 17 00:00:00 2001 From: Nathan_akin <85641756+akintewe@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:50:58 +0000 Subject: [PATCH 1/2] feat: implement logout functionality - Add logoutUser thunk handlers to authSlice with pending/fulfilled/rejected states - Integrate Redux in Navbar component to manage user authentication state - Implement logout flow: dispatch thunk, clear state, redirect to login with toast - Add loading state and disabled UI for logout button during processing - Handle logout failures gracefully by clearing local state anyway - Update both desktop and mobile sign out buttons with proper UX --- src/components/layout/Navbar.jsx | 55 ++++++++++++++++++++++---------- src/features/auth/authSlice.js | 23 +++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/components/layout/Navbar.jsx b/src/components/layout/Navbar.jsx index a06fc4a..bb6989e 100644 --- a/src/components/layout/Navbar.jsx +++ b/src/components/layout/Navbar.jsx @@ -1,13 +1,9 @@ import { useState, useRef, useEffect } from 'react'; import { Link, NavLink, useNavigate } from 'react-router-dom'; - -// ─── Replace with your real auth hook / context ─────────────────────────────── -// e.g. import { useAuth } from '../../contexts/AuthContext'; -const useAuth = () => ({ - user: null, // set to a user object when logged in, e.g. { name: 'Alice', avatarUrl: null } - logout: () => {}, -}); -// ───────────────────────────────────────────────────────────────────────────── +import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import { selectCurrentUser } from '../../features/auth/authSelectors'; +import { logoutUser } from '../../features/auth/authThunks'; +import { toastSuccess } from '../../utils/toast'; const NAV_LINKS = [ { label: 'How it works', to: '/how-it-works' }, @@ -25,11 +21,13 @@ function initials(name = '') { } export default function Navbar() { - const { user, logout } = useAuth(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); + const user = useAppSelector(selectCurrentUser); const [menuOpen, setMenuOpen] = useState(false); const [avatarOpen, setAvatarOpen] = useState(false); + const [isLoggingOut, setIsLoggingOut] = useState(false); const avatarRef = useRef(null); /* close avatar dropdown on outside click */ @@ -46,10 +44,24 @@ export default function Navbar() { /* close mobile menu on route change */ const handleNavClick = () => setMenuOpen(false); - const handleLogout = () => { + const handleLogout = async () => { setAvatarOpen(false); - logout(); - navigate('/'); + setIsLoggingOut(true); + try { + // Dispatch logoutUser thunk which calls POST /api/auth/logout + const result = await dispatch(logoutUser()).unwrap(); + // Show success toast + toastSuccess('You have been logged out.'); + // Redirect to login + navigate('/login'); + } catch (error) { + // Even if logout fails on the backend, clear local state and redirect + // This ensures user can't access protected routes if disconnected + toastSuccess('You have been logged out.'); + navigate('/login'); + } finally { + setIsLoggingOut(false); + } }; return ( @@ -356,8 +368,14 @@ export default function Navbar() { setAvatarOpen(false)}>Profile setAvatarOpen(false)}>Settings
-
)} @@ -402,8 +420,13 @@ export default function Navbar() { Profile Settings
- ) : ( diff --git a/src/features/auth/authSlice.js b/src/features/auth/authSlice.js index 39b3a13..ff76db1 100644 --- a/src/features/auth/authSlice.js +++ b/src/features/auth/authSlice.js @@ -1,4 +1,5 @@ import { createSlice } from '@reduxjs/toolkit'; +import { logoutUser } from './authThunks'; const initialState = { user: null, @@ -34,6 +35,28 @@ const authSlice = createSlice({ state.isLoading = false; }, }, + extraReducers: (builder) => { + builder + .addCase(logoutUser.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(logoutUser.fulfilled, (state) => { + state.user = null; + state.token = null; + state.isAuthenticated = false; + state.isLoading = false; + state.error = null; + }) + .addCase(logoutUser.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload?.message || 'Logout failed'; + // Still clear credentials on error to ensure user is logged out locally + state.user = null; + state.token = null; + state.isAuthenticated = false; + }); + }, }); export const { setCredentials, clearCredentials, setAuthLoading, setAuthError } = authSlice.actions; From 673df6b8f0ef7c557c2bed078dcd0878682d0eea Mon Sep 17 00:00:00 2001 From: Nathan_akin <85641756+akintewe@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:56:23 +0000 Subject: [PATCH 2/2] fix: remove unused variables in handleLogout - Remove unused 'result' variable from logoutUser dispatch - Remove unused 'error' parameter from catch block - Simplify error handling since we handle all cases identically --- src/components/layout/Navbar.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/layout/Navbar.jsx b/src/components/layout/Navbar.jsx index bb6989e..6d47ae8 100644 --- a/src/components/layout/Navbar.jsx +++ b/src/components/layout/Navbar.jsx @@ -49,12 +49,12 @@ export default function Navbar() { setIsLoggingOut(true); try { // Dispatch logoutUser thunk which calls POST /api/auth/logout - const result = await dispatch(logoutUser()).unwrap(); + await dispatch(logoutUser()).unwrap(); // Show success toast toastSuccess('You have been logged out.'); // Redirect to login navigate('/login'); - } catch (error) { + } catch { // Even if logout fails on the backend, clear local state and redirect // This ensures user can't access protected routes if disconnected toastSuccess('You have been logged out.');