All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m39s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Desktop changes: - Replace ImportButton component with MUI Button matching Export style - Use hidden file input with validation - Dark red/maroon button with consistent styling Mobile changes: - Update both Import and Export buttons to use primary-500 style - Consistent dark primary button appearance - Maintains 44px touch target requirement Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
604 lines
26 KiB
TypeScript
604 lines
26 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
import { useLogout } from '../../../core/auth/useLogout';
|
|
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 { useExportUserData } from '../hooks/useExportUserData';
|
|
import { useVehicles } from '../../vehicles/hooks/useVehicles';
|
|
import { useAdminAccess } from '../../../core/auth/useAdminAccess';
|
|
import { useNavigationStore } from '../../../core/store';
|
|
import { DeleteAccountModal } from './DeleteAccountModal';
|
|
import { PendingDeletionBanner } from './PendingDeletionBanner';
|
|
import { ImportButton } from '../components/ImportButton';
|
|
import { ImportDialog } from '../components/ImportDialog';
|
|
|
|
interface ToggleSwitchProps {
|
|
enabled: boolean;
|
|
onChange: () => void;
|
|
label: string;
|
|
description?: string;
|
|
}
|
|
|
|
const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
|
|
enabled,
|
|
onChange,
|
|
label,
|
|
description
|
|
}) => (
|
|
<div className="flex items-center justify-between py-2">
|
|
<div>
|
|
<p className="font-medium text-slate-800 dark:text-avus">{label}</p>
|
|
{description && (
|
|
<p className="text-sm text-slate-500 dark:text-titanio">{description}</p>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={onChange}
|
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
|
enabled ? 'bg-primary-500 dark:bg-primary-600' : 'bg-gray-200 dark:bg-gray-600'
|
|
}`}
|
|
>
|
|
<span
|
|
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
|
enabled ? 'translate-x-6' : 'translate-x-1'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
);
|
|
|
|
interface ModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
title: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children }) => {
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 dark:bg-black/50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-white dark:bg-scuro rounded-lg p-6 max-w-sm w-full">
|
|
<h3 className="text-lg font-semibold text-slate-800 dark:text-avus mb-4">{title}</h3>
|
|
{children}
|
|
<div className="flex justify-end mt-4">
|
|
<button
|
|
onClick={onClose}
|
|
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-lg font-medium"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const MobileSettingsScreen: React.FC = () => {
|
|
const { user } = useAuth0();
|
|
const { logout } = useLogout();
|
|
const { navigateToScreen } = useNavigationStore();
|
|
const { settings, updateSetting, isLoading, error } = useSettings();
|
|
const { data: profile, isLoading: profileLoading } = useProfile();
|
|
const updateProfileMutation = useUpdateProfile();
|
|
const exportMutation = useExportUserData();
|
|
const { data: vehicles, isLoading: vehiclesLoading } = useVehicles();
|
|
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('');
|
|
const [showImportDialog, setShowImportDialog] = useState(false);
|
|
const [importFile, setImportFile] = useState<File | null>(null);
|
|
|
|
// 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 handleExportData = () => {
|
|
setShowDataExport(false);
|
|
exportMutation.mutate();
|
|
};
|
|
|
|
const handleImportFileSelected = (file: File) => {
|
|
setImportFile(file);
|
|
setShowImportDialog(true);
|
|
};
|
|
|
|
const handleImportDialogClose = () => {
|
|
setShowImportDialog(false);
|
|
setImportFile(null);
|
|
};
|
|
|
|
|
|
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 (
|
|
<MobileContainer>
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<div className="text-center">
|
|
<div className="text-slate-500 mb-2">Loading settings...</div>
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-500 mx-auto"></div>
|
|
</div>
|
|
</div>
|
|
</MobileContainer>
|
|
);
|
|
}
|
|
|
|
// Error state
|
|
if (error) {
|
|
return (
|
|
<MobileContainer>
|
|
<div className="space-y-4 pb-20 p-4">
|
|
<GlassCard padding="md">
|
|
<div className="text-center py-8">
|
|
<p className="text-red-600 mb-4">Failed to load settings</p>
|
|
<p className="text-sm text-slate-600 mb-4">{error}</p>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="px-4 py-2 bg-primary-500 hover:bg-primary-600 text-white rounded-lg dark:bg-primary-600 dark:hover:bg-primary-700"
|
|
>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</GlassCard>
|
|
</div>
|
|
</MobileContainer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<MobileContainer>
|
|
<div className="space-y-4 pb-20 p-4">
|
|
{/* Header */}
|
|
<div className="text-center mb-6">
|
|
<h1 className="text-2xl font-bold text-slate-800 dark:text-avus">Settings</h1>
|
|
<p className="text-slate-500 dark:text-titanio mt-2">Manage your account and preferences</p>
|
|
</div>
|
|
|
|
{/* Pending Deletion Banner */}
|
|
<PendingDeletionBanner />
|
|
|
|
{/* Profile Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus">Profile</h2>
|
|
{!isEditingProfile && !profileLoading && (
|
|
<button
|
|
onClick={handleEditProfile}
|
|
className="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
|
|
style={{ minHeight: '44px', minWidth: '44px' }}
|
|
>
|
|
Edit
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{profileLoading ? (
|
|
<div className="flex justify-center py-8">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-500"></div>
|
|
</div>
|
|
) : isEditingProfile ? (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-avus mb-1">
|
|
Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={profile?.email || ''}
|
|
disabled
|
|
className="w-full px-3 py-2 border border-slate-300 dark:border-silverstone rounded-lg bg-slate-100 dark:bg-gray-800 text-slate-500 dark:text-gray-400"
|
|
style={{ fontSize: '16px', minHeight: '44px' }}
|
|
/>
|
|
<p className="text-xs text-slate-500 dark:text-titanio mt-1">Email is managed by your account provider</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-avus 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 dark:border-silverstone rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-nero dark:text-avus"
|
|
style={{ fontSize: '16px', minHeight: '44px' }}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-avus 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 dark:border-silverstone rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-nero dark:text-avus"
|
|
style={{ fontSize: '16px', minHeight: '44px' }}
|
|
/>
|
|
<p className="text-xs text-slate-500 dark:text-titanio 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 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-lg font-medium hover:bg-gray-300 dark:hover:bg-gray-600 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-primary-500 text-white rounded-lg font-medium hover:bg-primary-600 transition-colors disabled:opacity-50 flex items-center justify-center dark:bg-primary-600 dark:hover:bg-primary-700"
|
|
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-primary-500 flex items-center justify-center text-white font-semibold">
|
|
{profile?.displayName?.charAt(0) || user?.name?.charAt(0) || user?.email?.charAt(0)}
|
|
</div>
|
|
)}
|
|
<div className="min-w-0 flex-1">
|
|
<p className="font-medium text-slate-800 dark:text-avus truncate">
|
|
{profile?.displayName || user?.name || 'User'}
|
|
</p>
|
|
<p className="text-sm text-slate-500 dark:text-titanio truncate">{profile?.email || user?.email}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2 pt-3 border-t border-slate-200 dark:border-silverstone">
|
|
<div>
|
|
<p className="text-xs font-medium text-slate-500 dark:text-canna uppercase">Display Name</p>
|
|
<p className="text-sm text-slate-800 dark:text-avus">{profile?.displayName || 'Not set'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs font-medium text-slate-500 dark:text-canna uppercase">Notification Email</p>
|
|
<p className="text-sm text-slate-800 dark:text-avus">{profile?.notificationEmail || 'Using primary email'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* My Vehicles Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<div className="flex items-center gap-2">
|
|
<svg className="w-5 h-5 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 4H5c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h4l3 3 3-3h4c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM8 11V9h8v2m-8 4v-2h5v2" />
|
|
</svg>
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus">
|
|
My Vehicles
|
|
</h2>
|
|
{!vehiclesLoading && vehicles && (
|
|
<span className="text-sm text-slate-500 dark:text-titanio">({vehicles.length})</span>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={() => navigateToScreen('Vehicles')}
|
|
className="px-3 py-1.5 bg-primary-500 text-white rounded-lg text-sm font-medium hover:bg-primary-600 transition-colors dark:bg-primary-600 dark:hover:bg-primary-700"
|
|
style={{ minHeight: '44px', minWidth: '44px' }}
|
|
>
|
|
Manage
|
|
</button>
|
|
</div>
|
|
|
|
{vehiclesLoading ? (
|
|
<div className="flex justify-center py-4">
|
|
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary-500"></div>
|
|
</div>
|
|
) : !vehicles?.length ? (
|
|
<p className="text-sm text-slate-500 dark:text-titanio py-2">
|
|
No vehicles registered. Add your first vehicle to get started.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{vehicles.map((vehicle) => (
|
|
<div
|
|
key={vehicle.id}
|
|
className="p-3 bg-slate-50 dark:bg-nero rounded-lg"
|
|
>
|
|
<p className="font-medium text-slate-800 dark:text-avus">
|
|
{vehicle.year} {vehicle.make} {vehicle.model}
|
|
</p>
|
|
{vehicle.nickname && (
|
|
<p className="text-sm text-slate-500 dark:text-titanio">{vehicle.nickname}</p>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* Notifications Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus mb-4">Notifications</h2>
|
|
<div className="space-y-3">
|
|
<ToggleSwitch
|
|
enabled={settings.notifications.email}
|
|
onChange={() => updateSetting('notifications', {
|
|
...settings.notifications,
|
|
email: !settings.notifications.email
|
|
})}
|
|
label="Email Notifications"
|
|
description="Receive updates via email"
|
|
/>
|
|
<ToggleSwitch
|
|
enabled={settings.notifications.push}
|
|
onChange={() => updateSetting('notifications', {
|
|
...settings.notifications,
|
|
push: !settings.notifications.push
|
|
})}
|
|
label="Push Notifications"
|
|
description="Receive mobile push notifications"
|
|
/>
|
|
<ToggleSwitch
|
|
enabled={settings.notifications.maintenance}
|
|
onChange={() => updateSetting('notifications', {
|
|
...settings.notifications,
|
|
maintenance: !settings.notifications.maintenance
|
|
})}
|
|
label="Maintenance Reminders"
|
|
description="Get reminded about vehicle maintenance"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* Appearance & Units Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus mb-4">Appearance & Units</h2>
|
|
<div className="space-y-4">
|
|
<ToggleSwitch
|
|
enabled={settings.darkMode}
|
|
onChange={() => updateSetting('darkMode', !settings.darkMode)}
|
|
label="Dark Mode"
|
|
description="Switch to dark theme"
|
|
/>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium text-slate-800 dark:text-avus">Unit System</p>
|
|
<p className="text-sm text-slate-500 dark:text-titanio">
|
|
Currently using {settings.unitSystem === 'imperial' ? 'Miles, Gallons, MPG, USD' : 'Km, Liters, L/100km, EUR'}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => updateSetting('unitSystem', settings.unitSystem === 'imperial' ? 'metric' : 'imperial')}
|
|
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
|
|
>
|
|
{settings.unitSystem === 'imperial' ? 'Switch to Metric' : 'Switch to Imperial'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* Data Management Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus mb-4">Data Management</h2>
|
|
<div className="space-y-3">
|
|
<ImportButton onFileSelected={handleImportFileSelected} />
|
|
<p className="text-sm text-slate-500 dark:text-titanio">
|
|
Restore your data from a previous export
|
|
</p>
|
|
<button
|
|
onClick={() => setShowDataExport(true)}
|
|
className="w-full text-left p-3 bg-primary-500 text-white rounded-lg font-medium hover:bg-primary-600 transition-colors disabled:opacity-50 dark:bg-primary-600 dark:hover:bg-primary-700"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
Export My Data
|
|
</button>
|
|
<p className="text-sm text-slate-500 dark:text-titanio">
|
|
Download a copy of all your vehicle and fuel data
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* Security & Privacy Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-white mb-4">Security & Privacy</h2>
|
|
<div className="space-y-3">
|
|
<button
|
|
onClick={() => navigateToScreen('Security')}
|
|
className="w-full text-left p-4 bg-primary-50 text-primary-700 rounded-lg font-medium hover:bg-primary-100 transition-colors active:bg-primary-200 dark:bg-primary-900/20 dark:text-primary-300 dark:hover:bg-primary-900/30"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
<div className="font-semibold">Security Settings</div>
|
|
<div className="text-sm text-primary-500 mt-1 dark:text-primary-400">Password, passkeys, verification</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* Admin Console Section */}
|
|
{!adminLoading && isAdmin && (
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-primary-500 dark:text-primary-400 mb-4">Admin Console</h2>
|
|
<div className="space-y-3">
|
|
<button
|
|
onClick={() => navigateToScreen('AdminUsers')}
|
|
className="w-full text-left p-4 bg-primary-50 text-primary-700 rounded-lg font-medium hover:bg-primary-100 transition-colors active:bg-primary-200 dark:bg-primary-900/20 dark:text-primary-300 dark:hover:bg-primary-900/30"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
<div className="font-semibold">User Management</div>
|
|
<div className="text-sm text-primary-500 mt-1 dark:text-primary-400">Manage admin users and permissions</div>
|
|
</button>
|
|
<button
|
|
onClick={() => navigateToScreen('AdminCatalog')}
|
|
className="w-full text-left p-4 bg-primary-50 text-primary-700 rounded-lg font-medium hover:bg-primary-100 transition-colors active:bg-primary-200 dark:bg-primary-900/20 dark:text-primary-300 dark:hover:bg-primary-900/30"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
<div className="font-semibold">Vehicle Catalog</div>
|
|
<div className="text-sm text-primary-500 mt-1 dark:text-primary-400">Manage makes, models, and engines</div>
|
|
</button>
|
|
<button
|
|
onClick={() => navigateToScreen('AdminEmailTemplates')}
|
|
className="w-full text-left p-4 bg-primary-50 text-primary-700 rounded-lg font-medium hover:bg-primary-100 transition-colors active:bg-primary-200 dark:bg-primary-900/20 dark:text-primary-300 dark:hover:bg-primary-900/30"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
<div className="font-semibold">Email Templates</div>
|
|
<div className="text-sm text-primary-500 mt-1 dark:text-primary-400">Manage notification email templates</div>
|
|
</button>
|
|
<button
|
|
onClick={() => navigateToScreen('AdminBackup')}
|
|
className="w-full text-left p-4 bg-primary-50 text-primary-700 rounded-lg font-medium hover:bg-primary-100 transition-colors active:bg-primary-200 dark:bg-primary-900/20 dark:text-primary-300 dark:hover:bg-primary-900/30"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
<div className="font-semibold">Backup & Restore</div>
|
|
<div className="text-sm text-primary-500 mt-1 dark:text-primary-400">Create backups and restore data</div>
|
|
</button>
|
|
<button
|
|
onClick={() => navigateToScreen('AdminLogs')}
|
|
className="w-full text-left p-4 bg-primary-50 text-primary-700 rounded-lg font-medium hover:bg-primary-100 transition-colors active:bg-primary-200 dark:bg-primary-900/20 dark:text-primary-300 dark:hover:bg-primary-900/30"
|
|
style={{ minHeight: '44px' }}
|
|
>
|
|
<div className="font-semibold">Audit Logs</div>
|
|
<div className="text-sm text-primary-500 mt-1 dark:text-primary-400">View system activity and audit logs</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
)}
|
|
|
|
{/* Account Actions Section */}
|
|
<GlassCard padding="md">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus mb-4">Account Actions</h2>
|
|
<div className="space-y-3">
|
|
<button
|
|
onClick={handleLogout}
|
|
className="w-full py-3 px-4 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-lg text-left font-medium hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
|
>
|
|
Sign Out
|
|
</button>
|
|
<button
|
|
onClick={() => setShowDeleteConfirm(true)}
|
|
className="w-full py-3 px-4 bg-red-50 text-red-600 rounded-lg text-left font-medium hover:bg-red-100 transition-colors"
|
|
>
|
|
Delete Account
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{/* Data Export Modal */}
|
|
<Modal
|
|
isOpen={showDataExport}
|
|
onClose={() => setShowDataExport(false)}
|
|
title="Export Data"
|
|
>
|
|
<p className="text-slate-600 dark:text-titanio mb-4">
|
|
This will create a downloadable file containing all your vehicle data, fuel logs, and preferences.
|
|
</p>
|
|
<div className="flex space-x-3">
|
|
<button
|
|
onClick={() => setShowDataExport(false)}
|
|
className="flex-1 py-2 px-4 bg-gray-200 text-gray-700 rounded-lg font-medium"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleExportData}
|
|
disabled={exportMutation.isPending}
|
|
className="flex-1 py-2 px-4 bg-primary-500 text-white rounded-lg font-medium hover:bg-primary-600 transition-colors disabled:opacity-50 dark:bg-primary-600 dark:hover:bg-primary-700"
|
|
>
|
|
{exportMutation.isPending ? 'Exporting...' : 'Export'}
|
|
</button>
|
|
</div>
|
|
</Modal>
|
|
|
|
{/* Delete Account Modal */}
|
|
<DeleteAccountModal
|
|
isOpen={showDeleteConfirm}
|
|
onClose={() => setShowDeleteConfirm(false)}
|
|
/>
|
|
|
|
{/* Import Dialog */}
|
|
<ImportDialog
|
|
isOpen={showImportDialog}
|
|
onClose={handleImportDialogClose}
|
|
file={importFile}
|
|
/>
|
|
</div>
|
|
</MobileContainer>
|
|
);
|
|
}; |