Skip to content

Commit d5250e4

Browse files
authored
Update navbar user status cues
1 parent 809f9f6 commit d5250e4

2 files changed

Lines changed: 36 additions & 9 deletions

File tree

frontend/src/components/Navbar.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import { getCurrentUser } from '../components/currentUserCache';
1010

1111
export const Navbar: React.FC = () => {
1212
const [appVersion, setAppVersion] = useState<string>('\u2026');
13-
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
13+
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
1414
const [currentUser, setCurrentUser] = useState<string>('\u2026');
15+
const isAuthenticatedUser = isAuthenticated === true;
16+
const isReadOnlyUser = isAuthenticated === false;
17+
const userLabel = isReadOnlyUser ? 'unknown' : currentUser;
1518

1619
useEffect(() => {
1720
let mounted = true;
@@ -75,7 +78,7 @@ export const Navbar: React.FC = () => {
7578
<a className="nav-link text-white" target="_blank" rel="noopener noreferrer" href="/admin">
7679
Django-Admin
7780
</a>
78-
{isAuthenticated ? (
81+
{isAuthenticatedUser ? (
7982
<Link className="nav-link text-white" to="/logout">
8083
Logout
8184
</Link>
@@ -100,16 +103,36 @@ export const Navbar: React.FC = () => {
100103
id="userDropdown"
101104
data-bs-toggle="dropdown"
102105
aria-expanded="false"
103-
style={{ backgroundColor: '#9370DB', color: '#fff', border: 'none' }}
106+
style={{
107+
backgroundColor: isReadOnlyUser ? '#6c757d' : '#9370DB',
108+
color: '#fff',
109+
border: 'none',
110+
}}
104111
>
105-
User: {currentUser}
112+
User ID: {userLabel}{isReadOnlyUser ? ' (read only)' : ''}
106113
</button>
107114
<ul className="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
108115
<li>
109116
<span className="dropdown-item-text" id="currentUserId">
110-
{currentUser}
117+
{userLabel}
111118
</span>
112119
</li>
120+
<li>
121+
<span className="dropdown-item-text">
122+
{isReadOnlyUser
123+
? 'Status: read only'
124+
: isAuthenticatedUser
125+
? 'Status: logged in'
126+
: 'Status: checking access'}
127+
</span>
128+
</li>
129+
{isReadOnlyUser && (
130+
<li>
131+
<Link className="dropdown-item" to="/login">
132+
Login to edit bookings
133+
</Link>
134+
</li>
135+
)}
113136
</ul>
114137
</div>
115138
</div>

frontend/src/components/__tests__/Navbar.test.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ describe('Navbar', () => {
102102
(getAuthStatus as jest.Mock).mockResolvedValue({ authenticated: false });
103103
(getCurrentUser as jest.Mock).mockImplementation(() => new Promise(() => {}));
104104
renderWithRouter(<Navbar />);
105-
expect(screen.getByText(/User: \u2026/)).toBeInTheDocument();
105+
expect(screen.getByText(/User ID: \u2026/)).toBeInTheDocument();
106+
expect(screen.getByText('Status: checking access')).toBeInTheDocument();
106107
});
107108

108109
it('fetches and displays the current user on success', async () => {
@@ -111,17 +112,20 @@ describe('Navbar', () => {
111112
(getCurrentUser as jest.Mock).mockResolvedValue('admin');
112113
renderWithRouter(<Navbar />);
113114
await waitFor(() => {
114-
expect(screen.getByText('User: admin')).toBeInTheDocument();
115+
expect(screen.getByText('User ID: admin')).toBeInTheDocument();
116+
expect(screen.getByText('Status: logged in')).toBeInTheDocument();
115117
});
116118
});
117119

118-
it('displays "unknown" for user if fetch fails', async () => {
120+
it('shows read only user state and login prompt when unauthenticated', async () => {
119121
(getAppVersion as jest.Mock).mockResolvedValue('1.2.3');
120122
(getAuthStatus as jest.Mock).mockResolvedValue({ authenticated: false });
121123
(getCurrentUser as jest.Mock).mockRejectedValue(new Error('Network error'));
122124
renderWithRouter(<Navbar />);
123125
await waitFor(() => {
124-
expect(screen.getByText('User: unknown')).toBeInTheDocument();
126+
expect(screen.getByText('User ID: unknown (read only)')).toBeInTheDocument();
127+
expect(screen.getByText('Status: read only')).toBeInTheDocument();
128+
expect(screen.getByRole('link', { name: 'Login to edit bookings' })).toHaveAttribute('href', '/login');
125129
});
126130
});
127131

0 commit comments

Comments
 (0)