Notification updates

This commit is contained in:
Eric Gullickson
2025-12-21 19:56:52 -06:00
parent 144f1d5bb0
commit 719c80ecd8
80 changed files with 7552 additions and 678 deletions

View File

@@ -7,6 +7,7 @@ import { useAuth0 } from '@auth0/auth0-react';
import { useNavigate } from 'react-router-dom';
import { useUnits } from '../core/units/UnitsContext';
import { useAdminAccess } from '../core/auth/useAdminAccess';
import { useProfile, useUpdateProfile } from '../features/settings/hooks/useProfile';
import {
Box,
Typography,
@@ -21,7 +22,9 @@ import {
Button as MuiButton,
Select,
MenuItem,
FormControl
FormControl,
TextField,
CircularProgress
} from '@mui/material';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import NotificationsIcon from '@mui/icons-material/Notifications';
@@ -29,6 +32,9 @@ 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 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 = () => {
@@ -40,10 +46,59 @@ export const SettingsPage: React.FC = () => {
const [emailUpdates, setEmailUpdates] = useState(false);
const [darkMode, setDarkMode] = useState(false);
// Profile state
const { data: profile, isLoading: profileLoading } = useProfile();
const updateProfileMutation = useUpdateProfile();
const [isEditingProfile, setIsEditingProfile] = useState(false);
const [editedDisplayName, setEditedDisplayName] = useState('');
const [editedNotificationEmail, setEditedNotificationEmail] = useState('');
// 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({ logoutParams: { returnTo: window.location.origin } });
};
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
}
};
return (
<Box sx={{ py: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, color: 'text.primary', mb: 4 }}>
@@ -51,69 +106,149 @@ export const SettingsPage: React.FC = () => {
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* Account Section */}
{/* Profile Section */}
<Card>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3 }}>
Account
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
<Avatar
sx={{
width: 64,
height: 64,
bgcolor: 'primary.main',
fontSize: '1.5rem',
fontWeight: 600,
mr: 3
}}
>
{user?.name?.charAt(0) || user?.email?.charAt(0)}
</Avatar>
<Box>
<Typography variant="h6" sx={{ fontWeight: 500 }}>
{user?.name || 'User'}
</Typography>
<Typography variant="body2" color="text.secondary">
{user?.email}
</Typography>
<Typography variant="caption" color="text.secondary">
Verified account
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Profile
</Typography>
{!isEditingProfile && !profileLoading && (
<MuiButton
variant="outlined"
size="small"
startIcon={<EditIcon />}
onClick={handleEditProfile}
>
Edit
</MuiButton>
)}
</Box>
<List disablePadding>
<ListItem>
<ListItemIcon>
<AccountCircleIcon />
</ListItemIcon>
<ListItemText
primary="Profile Information"
secondary="Manage your account details"
/>
<ListItemSecondaryAction>
<MuiButton variant="outlined" size="small">
Edit
</MuiButton>
</ListItemSecondaryAction>
</ListItem>
<Divider />
<ListItem>
<ListItemIcon>
<SecurityIcon />
</ListItemIcon>
<ListItemText
primary="Security & Privacy"
secondary="Password, two-factor authentication"
/>
<ListItemSecondaryAction>
<MuiButton variant="outlined" size="small">
Manage
</MuiButton>
</ListItemSecondaryAction>
</ListItem>
</List>
{profileLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</Box>
) : (
<>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
<Avatar
sx={{
width: 64,
height: 64,
bgcolor: 'primary.main',
fontSize: '1.5rem',
fontWeight: 600,
mr: 3
}}
>
{profile?.displayName?.charAt(0) || user?.name?.charAt(0) || user?.email?.charAt(0)}
</Avatar>
<Box>
<Typography variant="h6" sx={{ fontWeight: 500 }}>
{profile?.displayName || user?.name || 'User'}
</Typography>
<Typography variant="body2" color="text.secondary">
{profile?.email || user?.email}
</Typography>
<Typography variant="caption" color="text.secondary">
Verified account
</Typography>
</Box>
</Box>
{isEditingProfile ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<TextField
label="Email"
value={profile?.email || ''}
disabled
fullWidth
helperText="Email is managed by your account provider and cannot be changed here"
variant="outlined"
/>
<TextField
label="Display Name"
value={editedDisplayName}
onChange={(e) => setEditedDisplayName(e.target.value)}
fullWidth
placeholder="Enter your display name"
variant="outlined"
/>
<TextField
label="Notification Email"
value={editedNotificationEmail}
onChange={(e) => 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"
/>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
<MuiButton
variant="outlined"
startIcon={<CancelIcon />}
onClick={handleCancelEdit}
disabled={updateProfileMutation.isPending}
>
Cancel
</MuiButton>
<MuiButton
variant="contained"
startIcon={updateProfileMutation.isPending ? <CircularProgress size={20} /> : <SaveIcon />}
onClick={handleSaveProfile}
disabled={updateProfileMutation.isPending}
>
Save
</MuiButton>
</Box>
</Box>
) : (
<List disablePadding>
<ListItem>
<ListItemIcon>
<AccountCircleIcon />
</ListItemIcon>
<ListItemText
primary="Email"
secondary={profile?.email || user?.email || 'Not available'}
/>
</ListItem>
<Divider />
<ListItem>
<ListItemText
primary="Display Name"
secondary={profile?.displayName || 'Not set'}
sx={{ pl: 7 }}
/>
</ListItem>
<Divider />
<ListItem>
<ListItemText
primary="Notification Email"
secondary={profile?.notificationEmail || 'Using primary email'}
sx={{ pl: 7 }}
/>
</ListItem>
<Divider />
<ListItem>
<ListItemIcon>
<SecurityIcon />
</ListItemIcon>
<ListItemText
primary="Security & Privacy"
secondary="Password, two-factor authentication"
/>
<ListItemSecondaryAction>
<MuiButton variant="outlined" size="small">
Manage
</MuiButton>
</ListItemSecondaryAction>
</ListItem>
</List>
)}
</>
)}
</Card>
{/* Notifications Section */}
@@ -306,6 +441,23 @@ export const SettingsPage: React.FC = () => {
</MuiButton>
</ListItemSecondaryAction>
</ListItem>
<Divider />
<ListItem>
<ListItemText
primary="Email Templates"
secondary="Manage notification email templates"
sx={{ pl: 7 }}
/>
<ListItemSecondaryAction>
<MuiButton
variant="outlined"
size="small"
onClick={() => navigate('/garage/settings/admin/email-templates')}
>
Manage
</MuiButton>
</ListItemSecondaryAction>
</ListItem>
</List>
</Card>
)}