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

@@ -3,6 +3,7 @@ import { useAuth0 } from '@auth0/auth0-react';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { MobileContainer } from '../../../shared-minimal/components/mobile/MobileContainer';
import { useSettings } from '../hooks/useSettings';
import { useProfile, useUpdateProfile } from '../hooks/useProfile';
import { useAdminAccess } from '../../../core/auth/useAdminAccess';
import { useNavigationStore } from '../../../core/store';
@@ -73,9 +74,22 @@ export const MobileSettingsScreen: React.FC = () => {
const { user, logout } = useAuth0();
const { navigateToScreen } = useNavigationStore();
const { settings, updateSetting, isLoading, error } = useSettings();
const { data: profile, isLoading: profileLoading } = useProfile();
const updateProfileMutation = useUpdateProfile();
const { isAdmin, loading: adminLoading } = useAdminAccess();
const [showDataExport, setShowDataExport] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
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({
@@ -97,6 +111,40 @@ export const MobileSettingsScreen: React.FC = () => {
setShowDeleteConfirm(false);
};
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
}
};
// Loading state
if (isLoading) {
return (
@@ -142,28 +190,128 @@ export const MobileSettingsScreen: React.FC = () => {
<p className="text-slate-500 mt-2">Manage your account and preferences</p>
</div>
{/* Account Section */}
{/* Profile Section */}
<GlassCard padding="md">
<div>
<h2 className="text-lg font-semibold text-slate-800 mb-4">Account</h2>
<div className="flex items-center space-x-3">
{user?.picture && (
<img
src={user.picture}
alt="Profile"
className="w-12 h-12 rounded-full"
/>
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold text-slate-800">Profile</h2>
{!isEditingProfile && !profileLoading && (
<button
onClick={handleEditProfile}
className="px-3 py-1.5 bg-blue-100 text-blue-700 rounded-lg text-sm font-medium hover:bg-blue-200 transition-colors"
style={{ minHeight: '44px', minWidth: '44px' }}
>
Edit
</button>
)}
<div>
<p className="font-medium text-slate-800">{user?.name}</p>
<p className="text-sm text-slate-500">{user?.email}</p>
</div>
{profileLoading ? (
<div className="flex justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>
<div className="pt-3 mt-3 border-t border-slate-200">
<p className="text-sm text-slate-600">
Member since {user?.updated_at ? new Date(user.updated_at).toLocaleDateString() : 'Unknown'}
</p>
</div>
) : isEditingProfile ? (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Email
</label>
<input
type="email"
value={profile?.email || ''}
disabled
className="w-full px-3 py-2 border border-slate-300 rounded-lg bg-slate-100 text-slate-500"
style={{ fontSize: '16px', minHeight: '44px' }}
/>
<p className="text-xs text-slate-500 mt-1">Email is managed by your account provider</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Display Name
</label>
<input
type="text"
value={editedDisplayName}
onChange={(e) => setEditedDisplayName(e.target.value)}
placeholder="Enter your display name"
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
style={{ fontSize: '16px', minHeight: '44px' }}
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Notification Email
</label>
<input
type="email"
value={editedNotificationEmail}
onChange={(e) => setEditedNotificationEmail(e.target.value)}
placeholder="Leave blank to use your primary email"
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
style={{ fontSize: '16px', minHeight: '44px' }}
/>
<p className="text-xs text-slate-500 mt-1">Optional: Use a different email for notifications</p>
</div>
<div className="flex space-x-3 pt-2">
<button
onClick={handleCancelEdit}
disabled={updateProfileMutation.isPending}
className="flex-1 py-2.5 px-4 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300 transition-colors disabled:opacity-50"
style={{ minHeight: '44px' }}
>
Cancel
</button>
<button
onClick={handleSaveProfile}
disabled={updateProfileMutation.isPending}
className="flex-1 py-2.5 px-4 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center justify-center"
style={{ minHeight: '44px' }}
>
{updateProfileMutation.isPending ? (
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
) : (
'Save'
)}
</button>
</div>
</div>
) : (
<div>
<div className="flex items-center space-x-3 mb-4">
{user?.picture ? (
<img
src={user.picture}
alt="Profile"
className="w-12 h-12 rounded-full"
/>
) : (
<div className="w-12 h-12 rounded-full bg-blue-600 flex items-center justify-center text-white font-semibold">
{profile?.displayName?.charAt(0) || user?.name?.charAt(0) || user?.email?.charAt(0)}
</div>
)}
<div>
<p className="font-medium text-slate-800">
{profile?.displayName || user?.name || 'User'}
</p>
<p className="text-sm text-slate-500">{profile?.email || user?.email}</p>
</div>
</div>
<div className="space-y-2 pt-3 border-t border-slate-200">
<div>
<p className="text-xs font-medium text-slate-500 uppercase">Display Name</p>
<p className="text-sm text-slate-800">{profile?.displayName || 'Not set'}</p>
</div>
<div>
<p className="text-xs font-medium text-slate-500 uppercase">Notification Email</p>
<p className="text-sm text-slate-800">{profile?.notificationEmail || 'Using primary email'}</p>
</div>
</div>
</div>
)}
</div>
</GlassCard>
@@ -281,6 +429,14 @@ export const MobileSettingsScreen: React.FC = () => {
<div className="font-semibold">Station Management</div>
<div className="text-sm text-blue-600 mt-1">Manage gas station data and locations</div>
</button>
<button
onClick={() => navigateToScreen('AdminEmailTemplates')}
className="w-full text-left p-4 bg-blue-50 text-blue-700 rounded-lg font-medium hover:bg-blue-100 transition-colors active:bg-blue-200"
style={{ minHeight: '44px' }}
>
<div className="font-semibold">Email Templates</div>
<div className="text-sm text-blue-600 mt-1">Manage notification email templates</div>
</button>
</div>
</div>
</GlassCard>