/**
* @ai-summary Desktop admin page for user management
* @ai-context List users, filter, search, change tiers, deactivate/reactivate
*/
import React, { useState, useCallback } from 'react';
import { Navigate } from 'react-router-dom';
import {
Box,
Button,
Chip,
CircularProgress,
Collapse,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
IconButton,
InputAdornment,
InputLabel,
Menu,
MenuItem,
Paper,
Select,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
TextField,
Tooltip,
Typography,
SelectChangeEvent,
} from '@mui/material';
import {
Search,
Clear,
MoreVert,
AdminPanelSettings,
PersonOff,
PersonAdd,
Edit,
Security,
DeleteForever,
DirectionsCar,
KeyboardArrowDown,
KeyboardArrowUp,
} from '@mui/icons-material';
import { useAdminAccess } from '../../core/auth/useAdminAccess';
import {
useUsers,
useUpdateUserTier,
useDeactivateUser,
useReactivateUser,
useUpdateUserProfile,
usePromoteToAdmin,
useHardDeleteUser,
useAdminStats,
useUserVehicles,
} from '../../features/admin/hooks/useUsers';
import {
ManagedUser,
SubscriptionTier,
ListUsersParams,
} from '../../features/admin/types/admin.types';
import { AdminSectionHeader } from '../../features/admin/components';
const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
// Expandable vehicle row component
const UserVehiclesRow: React.FC<{ userId: string; isOpen: boolean }> = ({ userId, isOpen }) => {
const { data, isLoading, error } = useUserVehicles(userId);
if (!isOpen) return null;
return (
Vehicles
{isLoading ? (
) : error ? (
Failed to load vehicles
) : !data?.vehicles?.length ? (
No vehicles registered
) : (
Year
Make
Model
{data.vehicles.map((vehicle, idx) => (
{vehicle.year}
{vehicle.make}
{vehicle.model}
))}
)}
);
};
export const AdminUsersPage: React.FC = () => {
const { isAdmin, loading: adminLoading } = useAdminAccess();
// Filter state
const [params, setParams] = useState({
page: 1,
pageSize: 20,
status: 'all',
sortBy: 'createdAt',
sortOrder: 'desc',
});
const [searchInput, setSearchInput] = useState('');
// Query
const { data, isLoading, error } = useUsers(params);
// Mutations
const updateTierMutation = useUpdateUserTier();
const deactivateMutation = useDeactivateUser();
const reactivateMutation = useReactivateUser();
const updateProfileMutation = useUpdateUserProfile();
const promoteToAdminMutation = usePromoteToAdmin();
const hardDeleteMutation = useHardDeleteUser();
// Action menu state
const [anchorEl, setAnchorEl] = useState(null);
const [selectedUser, setSelectedUser] = useState(null);
// Deactivate dialog state
const [deactivateDialogOpen, setDeactivateDialogOpen] = useState(false);
const [deactivateReason, setDeactivateReason] = useState('');
// Edit dialog state
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [editEmail, setEditEmail] = useState('');
const [editDisplayName, setEditDisplayName] = useState('');
// Promote to admin dialog state
const [promoteDialogOpen, setPromoteDialogOpen] = useState(false);
const [promoteRole, setPromoteRole] = useState<'admin' | 'super_admin'>('admin');
// Hard delete dialog state
const [hardDeleteDialogOpen, setHardDeleteDialogOpen] = useState(false);
const [hardDeleteReason, setHardDeleteReason] = useState('');
const [hardDeleteConfirmText, setHardDeleteConfirmText] = useState('');
// Expanded row state for vehicle list
const [expandedRow, setExpandedRow] = useState(null);
// Admin stats query
const { data: statsData } = useAdminStats();
// Handlers
const handleSearch = useCallback(() => {
setParams(prev => ({ ...prev, search: searchInput || undefined, page: 1 }));
}, [searchInput]);
const handleSearchKeyPress = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
handleSearch();
}
},
[handleSearch]
);
const handleClearSearch = useCallback(() => {
setSearchInput('');
setParams(prev => ({ ...prev, search: undefined, page: 1 }));
}, []);
const handlePageChange = useCallback((_: unknown, newPage: number) => {
setParams(prev => ({ ...prev, page: newPage + 1 }));
}, []);
const handleRowsPerPageChange = useCallback(
(event: React.ChangeEvent) => {
setParams(prev => ({ ...prev, pageSize: parseInt(event.target.value, 10), page: 1 }));
},
[]
);
const handleTierFilterChange = useCallback((event: SelectChangeEvent) => {
const value = event.target.value;
setParams(prev => ({
...prev,
tier: value ? (value as SubscriptionTier) : undefined,
page: 1,
}));
}, []);
const handleStatusFilterChange = useCallback((event: SelectChangeEvent) => {
setParams(prev => ({
...prev,
status: event.target.value as 'active' | 'deactivated' | 'all',
page: 1,
}));
}, []);
const handleTierChange = useCallback(
(userId: string, newTier: SubscriptionTier) => {
updateTierMutation.mutate({ userId, data: { subscriptionTier: newTier } });
},
[updateTierMutation]
);
const handleMenuOpen = useCallback((event: React.MouseEvent, user: ManagedUser) => {
setAnchorEl(event.currentTarget);
setSelectedUser(user);
}, []);
const handleMenuClose = useCallback(() => {
setAnchorEl(null);
setSelectedUser(null);
}, []);
const handleDeactivateClick = useCallback(() => {
setDeactivateDialogOpen(true);
setAnchorEl(null);
}, []);
const handleDeactivateConfirm = useCallback(() => {
if (selectedUser) {
deactivateMutation.mutate(
{ userId: selectedUser.id, data: { reason: deactivateReason || undefined } },
{
onSuccess: () => {
setDeactivateDialogOpen(false);
setDeactivateReason('');
setSelectedUser(null);
},
}
);
}
}, [selectedUser, deactivateReason, deactivateMutation]);
const handleReactivate = useCallback(() => {
if (selectedUser) {
reactivateMutation.mutate(selectedUser.id);
setAnchorEl(null);
setSelectedUser(null);
}
}, [selectedUser, reactivateMutation]);
const handleEditClick = useCallback(() => {
if (selectedUser) {
setEditEmail(selectedUser.email);
setEditDisplayName(selectedUser.displayName || '');
setEditDialogOpen(true);
setAnchorEl(null);
}
}, [selectedUser]);
const handleEditConfirm = useCallback(() => {
if (selectedUser) {
const updates: { email?: string; displayName?: string } = {};
if (editEmail !== selectedUser.email) {
updates.email = editEmail;
}
if (editDisplayName !== (selectedUser.displayName || '')) {
updates.displayName = editDisplayName;
}
if (Object.keys(updates).length > 0) {
updateProfileMutation.mutate(
{ userId: selectedUser.id, data: updates },
{
onSuccess: () => {
setEditDialogOpen(false);
setEditEmail('');
setEditDisplayName('');
setSelectedUser(null);
},
}
);
}
}
}, [selectedUser, editEmail, editDisplayName, updateProfileMutation]);
const handleEditCancel = useCallback(() => {
setEditDialogOpen(false);
setEditEmail('');
setEditDisplayName('');
setSelectedUser(null);
}, []);
const handlePromoteClick = useCallback(() => {
setPromoteRole('admin');
setPromoteDialogOpen(true);
setAnchorEl(null);
}, []);
const handlePromoteConfirm = useCallback(() => {
if (selectedUser) {
promoteToAdminMutation.mutate(
{ userId: selectedUser.id, data: { role: promoteRole } },
{
onSuccess: () => {
setPromoteDialogOpen(false);
setPromoteRole('admin');
setSelectedUser(null);
},
}
);
}
}, [selectedUser, promoteRole, promoteToAdminMutation]);
const handlePromoteCancel = useCallback(() => {
setPromoteDialogOpen(false);
setPromoteRole('admin');
setSelectedUser(null);
}, []);
const handleHardDeleteClick = useCallback(() => {
setHardDeleteDialogOpen(true);
setAnchorEl(null);
}, []);
const handleHardDeleteConfirm = useCallback(() => {
if (selectedUser && hardDeleteConfirmText === 'DELETE') {
hardDeleteMutation.mutate(
{ userId: selectedUser.id, reason: hardDeleteReason || undefined },
{
onSuccess: () => {
setHardDeleteDialogOpen(false);
setHardDeleteReason('');
setHardDeleteConfirmText('');
setSelectedUser(null);
},
}
);
}
}, [selectedUser, hardDeleteReason, hardDeleteConfirmText, hardDeleteMutation]);
const handleHardDeleteCancel = useCallback(() => {
setHardDeleteDialogOpen(false);
setHardDeleteReason('');
setHardDeleteConfirmText('');
setSelectedUser(null);
}, []);
// Loading state
if (adminLoading) {
return (
);
}
// Not admin redirect
if (!isAdmin) {
return ;
}
const users = data?.users || [];
const total = data?.total || 0;
return (
{/* Filters Bar */}
{/* Search Input */}
setSearchInput(e.target.value)}
onKeyPress={handleSearchKeyPress}
size="small"
sx={{ flex: 1, minWidth: 250 }}
InputProps={{
startAdornment: (
),
endAdornment: searchInput && (
),
}}
/>
{/* Search Button */}
{/* Tier Filter */}
Tier
{/* Status Filter */}
Status
{/* Users Table */}
{isLoading ? (
) : error ? (
Failed to load users. Please try again.
) : users.length === 0 ? (
No users found matching your criteria.
) : (
<>
Email
Display Name
Tier
Vehicles
Status
Admin
Created
Actions
{users.map((user) => (
*': { borderBottom: expandedRow === user.id ? 'unset' : undefined },
}}
>
{user.email}
{user.displayName || '-'}
{user.vehicleCount > 0 && (
setExpandedRow(
expandedRow === user.id ? null : user.id
)}
aria-label="show vehicles"
sx={{ minWidth: 44, minHeight: 44 }}
>
{expandedRow === user.id ? (
) : (
)}
)}
{user.vehicleCount}
{user.isAdmin && (
)}
{new Date(user.createdAt).toLocaleDateString()}
handleMenuOpen(e, user)}
>
))}
>
)}
{/* Action Menu */}
{/* Deactivate Confirmation Dialog */}
{/* Edit User Dialog */}
{/* Promote to Admin Dialog */}
{/* Hard Delete Confirmation Dialog */}
);
};