/** * @ai-summary Settings page component for desktop application */ import React, { useState } from 'react'; import { useAuth0 } from '@auth0/auth0-react'; import { useNavigate } from 'react-router-dom'; import { useLogout } from '../core/auth/useLogout'; 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'; import { ImportDialog } from '../features/settings/components/ImportDialog'; import toast from 'react-hot-toast'; import { Box, Typography, Switch, Divider, Avatar, List, ListItem, ListItemIcon, ListItemText, ListItemSecondaryAction, Button as MuiButton, Select, MenuItem, FormControl, TextField, CircularProgress } from '@mui/material'; import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import NotificationsIcon from '@mui/icons-material/Notifications'; 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'; import { Card } from '../shared-minimal/components/Card'; export const SettingsPage: React.FC = () => { const { user } = useAuth0(); const { logout } = useLogout(); const navigate = useNavigate(); const { unitSystem, setUnitSystem } = useUnits(); const { isAdmin, loading: adminLoading } = useAdminAccess(); const { isDarkMode, setDarkMode } = useTheme(); const [notifications, setNotifications] = useState(true); const [emailUpdates, setEmailUpdates] = useState(false); // 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(''); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [importDialogOpen, setImportDialogOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = React.useRef(null); const exportMutation = useExportUserData(); // Initialize edit form when profile loads or edit mode starts React.useEffect(() => { if (profile && isEditingProfile) { setEditedDisplayName(profile.displayName || ''); setEditedNotificationEmail(profile.notificationEmail || ''); } }, [profile, isEditingProfile]); const handleLogout = () => { logout(); }; const handleEditProfile = () => { setIsEditingProfile(true); }; const handleCancelEdit = () => { setIsEditingProfile(false); setEditedDisplayName(profile?.displayName || ''); setEditedNotificationEmail(profile?.notificationEmail || ''); }; const handleSaveProfile = async () => { const updates: { displayName?: string; notificationEmail?: string } = {}; if (editedDisplayName !== (profile?.displayName || '')) { updates.displayName = editedDisplayName; } if (editedNotificationEmail !== (profile?.notificationEmail || '')) { updates.notificationEmail = editedNotificationEmail || undefined; } if (Object.keys(updates).length === 0) { setIsEditingProfile(false); return; } try { await updateProfileMutation.mutateAsync(updates); setIsEditingProfile(false); } catch (error) { // Error is handled by the mutation hook } }; const handleImportClick = () => { fileInputRef.current?.click(); }; const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; // Validate file extension if (!file.name.endsWith('.tar.gz')) { toast.error('Please select a .tar.gz file'); return; } // Validate file size (max 500MB) const maxSize = 500 * 1024 * 1024; if (file.size > maxSize) { toast.error('File size exceeds 500MB limit'); return; } setSelectedFile(file); setImportDialogOpen(true); // Reset input so same file can be selected again event.target.value = ''; }; const handleImportClose = () => { setImportDialogOpen(false); setSelectedFile(null); }; return ( Settings {/* Profile Section */} Profile {!isEditingProfile && !profileLoading && ( } onClick={handleEditProfile} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Edit )} {profileLoading ? ( ) : ( <> {profile?.displayName?.charAt(0) || user?.name?.charAt(0) || user?.email?.charAt(0)} {profile?.displayName || user?.name || 'User'} {profile?.email || user?.email} Verified account {isEditingProfile ? ( setEditedDisplayName(e.target.value)} fullWidth placeholder="Enter your display name" variant="outlined" /> setEditedNotificationEmail(e.target.value)} fullWidth placeholder="Leave blank to use your primary email" helperText="Optional: Use a different email address for notifications" variant="outlined" type="email" /> } onClick={handleCancelEdit} disabled={updateProfileMutation.isPending} > Cancel : } onClick={handleSaveProfile} disabled={updateProfileMutation.isPending} > Save ) : ( navigate('/garage/settings/security')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Manage )} )} {/* My Vehicles Section */} My Vehicles {!vehiclesLoading && vehicles && ( ({vehicles.length}) )} navigate('/garage/vehicles')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Manage {vehiclesLoading ? ( ) : !vehicles?.length ? ( No vehicles registered. Add your first vehicle to get started. ) : ( {vehicles.map((vehicle, index) => ( {index > 0 && } ))} )} {/* Notifications Section */} Notifications setNotifications(e.target.checked)} color="primary" /> setEmailUpdates(e.target.checked)} color="primary" /> {/* Appearance & Units Section */} Appearance & Units setDarkMode(e.target.checked)} color="primary" /> {/* Data & Storage Section */} Data & Storage Import exportMutation.mutate()} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > {exportMutation.isPending ? 'Exporting...' : 'Export'} {/* Admin Console Section */} {!adminLoading && isAdmin && ( Admin Console navigate('/garage/settings/admin/users')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Manage navigate('/garage/settings/admin/catalog')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Manage navigate('/garage/settings/admin/email-templates')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Manage navigate('/garage/settings/admin/backup')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > Manage navigate('/garage/settings/admin/logs')} sx={{ backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark' } }} > View )} {/* Account Actions */} Account Actions Sign Out setDeleteDialogOpen(true)} sx={{ borderRadius: '999px' }} > Delete Account setDeleteDialogOpen(false)} /> ); };