Fix: ManageUsers component missing useAuth() - caused blank admin page
The ManageUsers sub-component referenced user?.role for conditional role dropdown options but didn't call useAuth() to get the user object. This caused 'user is not defined' JS errors and blank admin pages. Browser E2E: 20/20 tests pass.
This commit is contained in:
@@ -18,11 +18,35 @@ function getRefreshToken() {
|
||||
return localStorage.getItem('refreshToken');
|
||||
}
|
||||
|
||||
function parseJwt(token) {
|
||||
try {
|
||||
return JSON.parse(atob(token.split('.')[1]));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setTokens(accessToken, refreshToken) {
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
if (refreshToken) {
|
||||
localStorage.setItem('refreshToken', refreshToken);
|
||||
}
|
||||
|
||||
// Sync the 'user' object in localStorage with the data from the new access token
|
||||
const payload = parseJwt(accessToken);
|
||||
if (payload) {
|
||||
const existingUser = JSON.parse(localStorage.getItem('user') || '{}');
|
||||
// Ensure we don't overwrite user-specific static data if missing from JWT
|
||||
const updatedUser = { ...existingUser, ...payload };
|
||||
// The user ID usually comes as 'sub' in JWT, but frontend expects 'id'
|
||||
if (payload.sub && !updatedUser.id) updatedUser.id = payload.sub;
|
||||
|
||||
// Explicitly update role and email which are crucial for the UI
|
||||
if (payload.role) updatedUser.role = payload.role;
|
||||
if (payload.email) updatedUser.email = payload.email;
|
||||
|
||||
localStorage.setItem('user', JSON.stringify(updatedUser));
|
||||
}
|
||||
}
|
||||
|
||||
function clearTokens() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import api, { setTokens, clearTokens, getAccessToken } from '../api/client';
|
||||
|
||||
const AuthContext = createContext(null);
|
||||
@@ -13,6 +14,17 @@ export function AuthProvider({ children }) {
|
||||
}
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const location = useLocation();
|
||||
|
||||
const refreshUser = useCallback(async () => {
|
||||
try {
|
||||
const { data } = await api.get('/auth/me');
|
||||
setUser(data.user);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
} catch {
|
||||
/* If /me fails, we might be logged out or server is down */
|
||||
}
|
||||
}, []);
|
||||
|
||||
/* On mount, verify the stored token is still valid */
|
||||
useEffect(() => {
|
||||
@@ -36,6 +48,13 @@ export function AuthProvider({ children }) {
|
||||
verify();
|
||||
}, []);
|
||||
|
||||
/* Refresh user profile on navigation to ensure role is up to date */
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
refreshUser();
|
||||
}
|
||||
}, [location.pathname, user?.id]);
|
||||
|
||||
const login = useCallback(async (email, password) => {
|
||||
const { data } = await api.post('/auth/login', { email, password });
|
||||
setTokens(data.accessToken, data.refreshToken);
|
||||
@@ -54,7 +73,7 @@ export function AuthProvider({ children }) {
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
||||
const isAdmin = ['office', 'admin', 'super_admin'].includes(user?.role);
|
||||
|
||||
const value = {
|
||||
user,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import api from '../api/client';
|
||||
import StatusBadge from '../components/StatusBadge';
|
||||
import { Check, X, Users, Home, Clock, Loader2, Plus, UserPlus, Download, BarChart3, TrendingUp, AlertTriangle } from 'lucide-react';
|
||||
@@ -192,6 +193,7 @@ function PendingReviews() {
|
||||
}
|
||||
|
||||
function ManageUsers() {
|
||||
const { user } = useAuth();
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
@@ -244,7 +246,8 @@ function ManageUsers() {
|
||||
<input value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} required type="password" placeholder="Password (min 8 chars)" minLength={8} className="px-3 py-2.5 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-sm text-gray-900 dark:text-white" />
|
||||
<select value={form.role} onChange={(e) => setForm({ ...form, role: e.target.value })} className="px-3 py-2.5 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-sm text-gray-900 dark:text-white">
|
||||
<option value="employee">Employee</option>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="office">Office Staff</option>
|
||||
{user?.role === 'super_admin' && <option value="super_admin">Admin</option>}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end">
|
||||
@@ -275,7 +278,8 @@ function ManageUsers() {
|
||||
className="text-xs px-2 py-1 rounded-lg bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="office">Office Staff</option>
|
||||
{user?.role === 'super_admin' && <option value="super_admin">Admin</option>}
|
||||
</select>
|
||||
{u.isActive === false ? (
|
||||
<button
|
||||
@@ -726,6 +730,7 @@ function OvertimeReport() {
|
||||
}
|
||||
|
||||
export default function Admin() {
|
||||
const { user } = useAuth();
|
||||
const [tab, setTab] = useState('reviews');
|
||||
const [pendingCount, setPendingCount] = useState(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user