feat: Add admin vehicle management and profile vehicles display (refs #11)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m34s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

- Add GET /api/admin/stats endpoint for Total Vehicles widget
- Add GET /api/admin/users/:auth0Sub/vehicles endpoint for user vehicle list
- Update AdminUsersPage with Total Vehicles stat and expandable vehicle rows
- Add My Vehicles section to SettingsPage (desktop) and MobileSettingsScreen
- Update AdminUsersMobileScreen with stats header and vehicle expansion
- Add defense-in-depth admin checks and error handling
- Update admin README documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-04 13:18:38 -06:00
parent 2ec208e25a
commit 4fc5b391e1
10 changed files with 639 additions and 67 deletions

View File

@@ -9,6 +9,7 @@ import { useUnits } from '../core/units/UnitsContext';
import { useAdminAccess } from '../core/auth/useAdminAccess';
import { useProfile, useUpdateProfile } from '../features/settings/hooks/useProfile';
import { useExportUserData } from '../features/settings/hooks/useExportUserData';
import { useVehicles } from '../features/vehicles/hooks/useVehicles';
import { useTheme } from '../shared-minimal/theme/ThemeContext';
import { DeleteAccountDialog } from '../features/settings/components/DeleteAccountDialog';
import { PendingDeletionBanner } from '../features/settings/components/PendingDeletionBanner';
@@ -36,6 +37,7 @@ import PaletteIcon from '@mui/icons-material/Palette';
import SecurityIcon from '@mui/icons-material/Security';
import StorageIcon from '@mui/icons-material/Storage';
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
import EditIcon from '@mui/icons-material/Edit';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -53,6 +55,9 @@ export const SettingsPage: React.FC = () => {
// Profile state
const { data: profile, isLoading: profileLoading } = useProfile();
const updateProfileMutation = useUpdateProfile();
// Vehicles state (for My Vehicles section)
const { data: vehicles, isLoading: vehiclesLoading } = useVehicles();
const [isEditingProfile, setIsEditingProfile] = useState(false);
const [editedDisplayName, setEditedDisplayName] = useState('');
const [editedNotificationEmail, setEditedNotificationEmail] = useState('');
@@ -277,6 +282,62 @@ export const SettingsPage: React.FC = () => {
)}
</Card>
{/* My Vehicles Section */}
<Card>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<DirectionsCarIcon color="primary" />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
My Vehicles
</Typography>
{!vehiclesLoading && vehicles && (
<Typography variant="body2" color="text.secondary">
({vehicles.length})
</Typography>
)}
</Box>
<MuiButton
variant="contained"
size="small"
onClick={() => navigate('/garage')}
sx={{
backgroundColor: 'primary.main',
color: 'primary.contrastText',
'&:hover': {
backgroundColor: 'primary.dark'
}
}}
>
Manage
</MuiButton>
</Box>
{vehiclesLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 3 }}>
<CircularProgress size={24} />
</Box>
) : !vehicles?.length ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 2 }}>
No vehicles registered. Add your first vehicle to get started.
</Typography>
) : (
<List disablePadding>
{vehicles.map((vehicle, index) => (
<React.Fragment key={vehicle.id}>
{index > 0 && <Divider />}
<ListItem sx={{ py: 1.5 }}>
<ListItemText
primary={`${vehicle.year} ${vehicle.make} ${vehicle.model}`}
secondary={vehicle.nickname || undefined}
primaryTypographyProps={{ fontWeight: 500 }}
/>
</ListItem>
</React.Fragment>
))}
</List>
)}
</Card>
{/* Notifications Section */}
<Card>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3 }}>