445 lines
14 KiB
Markdown
445 lines
14 KiB
Markdown
# Mobile Settings Implementation Guide
|
|
|
|
## Overview
|
|
Complete implementation guide for creating a full-featured mobile settings screen that matches desktop functionality. This addresses the critical gap where desktop has comprehensive settings but mobile only has a placeholder.
|
|
|
|
## Current State Analysis
|
|
|
|
### Desktop Settings (Full Implementation)
|
|
**File**: `/home/egullickson/motovaultpro/frontend/src/pages/SettingsPage.tsx`
|
|
|
|
**Features**:
|
|
- Account management section
|
|
- Notifications settings
|
|
- Appearance & Units (dark mode, metric/imperial)
|
|
- Data export and management
|
|
- Account actions (logout, delete account)
|
|
|
|
### Mobile Settings (Placeholder Only)
|
|
**File**: `frontend/src/App.tsx` (lines 113-122)
|
|
|
|
**Current Implementation**:
|
|
```tsx
|
|
const SettingsScreen = () => (
|
|
<div className="space-y-4">
|
|
<GlassCard>
|
|
<div className="text-center py-12">
|
|
<h2 className="text-lg font-semibold text-slate-800 mb-2">Settings</h2>
|
|
<p className="text-slate-500">Coming soon - App settings and preferences</p>
|
|
</div>
|
|
</GlassCard>
|
|
</div>
|
|
);
|
|
```
|
|
|
|
## Implementation Strategy
|
|
|
|
### Step 1: Create Mobile Settings Directory Structure
|
|
Create dedicated mobile settings components following existing patterns:
|
|
|
|
```
|
|
frontend/src/features/settings/
|
|
├── mobile/
|
|
│ ├── MobileSettingsScreen.tsx # Main settings screen
|
|
│ ├── AccountSection.tsx # Account management
|
|
│ ├── NotificationsSection.tsx # Notification preferences
|
|
│ ├── AppearanceSection.tsx # Dark mode & units
|
|
│ ├── DataSection.tsx # Export & data management
|
|
│ └── AccountActionsSection.tsx # Logout & delete account
|
|
└── hooks/
|
|
├── useSettings.ts # Settings state management
|
|
└── useSettingsPersistence.ts # Settings persistence
|
|
```
|
|
|
|
### Step 2: Implement Mobile Settings Screen Component
|
|
|
|
**File**: `frontend/src/features/settings/mobile/MobileSettingsScreen.tsx`
|
|
|
|
```tsx
|
|
import React from 'react';
|
|
import { GlassCard, MobileContainer } from '../../../shared-minimal/components/mobile';
|
|
import { AccountSection } from './AccountSection';
|
|
import { NotificationsSection } from './NotificationsSection';
|
|
import { AppearanceSection } from './AppearanceSection';
|
|
import { DataSection } from './DataSection';
|
|
import { AccountActionsSection } from './AccountActionsSection';
|
|
|
|
export const MobileSettingsScreen: React.FC = () => {
|
|
return (
|
|
<MobileContainer>
|
|
<div className="space-y-4 pb-20"> {/* Bottom padding for nav */}
|
|
<div className="text-center mb-6">
|
|
<h1 className="text-2xl font-bold text-slate-800">Settings</h1>
|
|
<p className="text-slate-500 mt-2">Manage your account and preferences</p>
|
|
</div>
|
|
|
|
<AccountSection />
|
|
<NotificationsSection />
|
|
<AppearanceSection />
|
|
<DataSection />
|
|
<AccountActionsSection />
|
|
</div>
|
|
</MobileContainer>
|
|
);
|
|
};
|
|
```
|
|
|
|
### Step 3: Implement Settings Sections
|
|
|
|
#### Account Section Component
|
|
**File**: `frontend/src/features/settings/mobile/AccountSection.tsx`
|
|
|
|
```tsx
|
|
import React from 'react';
|
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
import { GlassCard } from '../../../shared-minimal/components/mobile';
|
|
|
|
export const AccountSection: React.FC = () => {
|
|
const { user } = useAuth0();
|
|
|
|
return (
|
|
<GlassCard>
|
|
<div className="p-4">
|
|
<h2 className="text-lg font-semibold text-slate-800 mb-4">Account</h2>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex items-center space-x-3">
|
|
<img
|
|
src={user?.picture}
|
|
alt="Profile"
|
|
className="w-12 h-12 rounded-full"
|
|
/>
|
|
<div>
|
|
<p className="font-medium text-slate-800">{user?.name}</p>
|
|
<p className="text-sm text-slate-500">{user?.email}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2 border-t border-slate-200">
|
|
<p className="text-sm text-slate-600">
|
|
Member since {new Date(user?.updated_at || '').toLocaleDateString()}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
);
|
|
};
|
|
```
|
|
|
|
#### Appearance Section Component
|
|
**File**: `frontend/src/features/settings/mobile/AppearanceSection.tsx`
|
|
|
|
```tsx
|
|
import React from 'react';
|
|
import { GlassCard } from '../../../shared-minimal/components/mobile';
|
|
import { useSettings } from '../hooks/useSettings';
|
|
|
|
export const AppearanceSection: React.FC = () => {
|
|
const { settings, updateSetting } = useSettings();
|
|
|
|
const toggleDarkMode = () => {
|
|
updateSetting('darkMode', !settings.darkMode);
|
|
};
|
|
|
|
const toggleUnitSystem = () => {
|
|
updateSetting('unitSystem', settings.unitSystem === 'imperial' ? 'metric' : 'imperial');
|
|
};
|
|
|
|
return (
|
|
<GlassCard>
|
|
<div className="p-4">
|
|
<h2 className="text-lg font-semibold text-slate-800 mb-4">Appearance & Units</h2>
|
|
|
|
<div className="space-y-4">
|
|
{/* Dark Mode Toggle */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium text-slate-800">Dark Mode</p>
|
|
<p className="text-sm text-slate-500">Switch to dark theme</p>
|
|
</div>
|
|
<button
|
|
onClick={toggleDarkMode}
|
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
|
settings.darkMode ? 'bg-blue-600' : 'bg-gray-200'
|
|
}`}
|
|
>
|
|
<span
|
|
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
|
settings.darkMode ? 'translate-x-6' : 'translate-x-1'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Unit System Toggle */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium text-slate-800">Unit System</p>
|
|
<p className="text-sm text-slate-500">
|
|
Currently using {settings.unitSystem === 'imperial' ? 'Miles & Gallons' : 'Kilometers & Liters'}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={toggleUnitSystem}
|
|
className="px-4 py-2 bg-blue-100 text-blue-700 rounded-lg text-sm font-medium"
|
|
>
|
|
{settings.unitSystem === 'imperial' ? 'Switch to Metric' : 'Switch to Imperial'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
);
|
|
};
|
|
```
|
|
|
|
#### Account Actions Section Component
|
|
**File**: `frontend/src/features/settings/mobile/AccountActionsSection.tsx`
|
|
|
|
```tsx
|
|
import React, { useState } from 'react';
|
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
import { GlassCard } from '../../../shared-minimal/components/mobile';
|
|
|
|
export const AccountActionsSection: React.FC = () => {
|
|
const { logout } = useAuth0();
|
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
|
|
const handleLogout = () => {
|
|
logout({
|
|
logoutParams: {
|
|
returnTo: window.location.origin
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleDeleteAccount = () => {
|
|
// Implementation for account deletion
|
|
setShowDeleteConfirm(false);
|
|
// Navigate to account deletion flow
|
|
};
|
|
|
|
return (
|
|
<GlassCard>
|
|
<div className="p-4">
|
|
<h2 className="text-lg font-semibold text-slate-800 mb-4">Account Actions</h2>
|
|
|
|
<div className="space-y-3">
|
|
<button
|
|
onClick={handleLogout}
|
|
className="w-full py-3 px-4 bg-gray-100 text-gray-700 rounded-lg text-left font-medium hover:bg-gray-200 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>
|
|
|
|
{/* Delete Confirmation Modal */}
|
|
{showDeleteConfirm && (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-white rounded-lg p-6 max-w-sm w-full">
|
|
<h3 className="text-lg font-semibold text-slate-800 mb-2">Delete Account</h3>
|
|
<p className="text-slate-600 mb-4">
|
|
This action cannot be undone. All your data will be permanently deleted.
|
|
</p>
|
|
<div className="flex space-x-3">
|
|
<button
|
|
onClick={() => setShowDeleteConfirm(false)}
|
|
className="flex-1 py-2 px-4 bg-gray-200 text-gray-700 rounded-lg font-medium"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleDeleteAccount}
|
|
className="flex-1 py-2 px-4 bg-red-600 text-white rounded-lg font-medium"
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</GlassCard>
|
|
);
|
|
};
|
|
```
|
|
|
|
### Step 4: Implement Settings State Management
|
|
|
|
#### Settings Hook
|
|
**File**: `frontend/src/features/settings/hooks/useSettings.ts`
|
|
|
|
```tsx
|
|
import { useState, useEffect } from 'react';
|
|
import { useSettingsPersistence } from './useSettingsPersistence';
|
|
|
|
export interface SettingsState {
|
|
darkMode: boolean;
|
|
unitSystem: 'imperial' | 'metric';
|
|
notifications: {
|
|
email: boolean;
|
|
push: boolean;
|
|
maintenance: boolean;
|
|
};
|
|
}
|
|
|
|
const defaultSettings: SettingsState = {
|
|
darkMode: false,
|
|
unitSystem: 'imperial',
|
|
notifications: {
|
|
email: true,
|
|
push: true,
|
|
maintenance: true,
|
|
},
|
|
};
|
|
|
|
export const useSettings = () => {
|
|
const { loadSettings, saveSettings } = useSettingsPersistence();
|
|
const [settings, setSettings] = useState<SettingsState>(defaultSettings);
|
|
|
|
useEffect(() => {
|
|
const savedSettings = loadSettings();
|
|
if (savedSettings) {
|
|
setSettings(savedSettings);
|
|
}
|
|
}, [loadSettings]);
|
|
|
|
const updateSetting = <K extends keyof SettingsState>(
|
|
key: K,
|
|
value: SettingsState[K]
|
|
) => {
|
|
const newSettings = { ...settings, [key]: value };
|
|
setSettings(newSettings);
|
|
saveSettings(newSettings);
|
|
};
|
|
|
|
return {
|
|
settings,
|
|
updateSetting,
|
|
};
|
|
};
|
|
```
|
|
|
|
#### Settings Persistence Hook
|
|
**File**: `frontend/src/features/settings/hooks/useSettingsPersistence.ts`
|
|
|
|
```tsx
|
|
import { useCallback } from 'react';
|
|
import { SettingsState } from './useSettings';
|
|
|
|
const SETTINGS_STORAGE_KEY = 'motovaultpro-mobile-settings';
|
|
|
|
export const useSettingsPersistence = () => {
|
|
const loadSettings = useCallback((): SettingsState | null => {
|
|
try {
|
|
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
|
return stored ? JSON.parse(stored) : null;
|
|
} catch (error) {
|
|
console.error('Error loading settings:', error);
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const saveSettings = useCallback((settings: SettingsState) => {
|
|
try {
|
|
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
|
} catch (error) {
|
|
console.error('Error saving settings:', error);
|
|
}
|
|
}, []);
|
|
|
|
return {
|
|
loadSettings,
|
|
saveSettings,
|
|
};
|
|
};
|
|
```
|
|
|
|
### Step 5: Update App.tsx Integration
|
|
|
|
**File**: `frontend/src/App.tsx`
|
|
|
|
Replace the existing placeholder SettingsScreen with:
|
|
|
|
```tsx
|
|
// Import the new component
|
|
import { MobileSettingsScreen } from './features/settings/mobile/MobileSettingsScreen';
|
|
|
|
// Replace the existing SettingsScreen component (around line 113)
|
|
const SettingsScreen = MobileSettingsScreen;
|
|
```
|
|
|
|
### Step 6: Integration with Existing Systems
|
|
|
|
#### Unit System Integration
|
|
Ensure mobile settings integrate with existing unit system:
|
|
|
|
**File**: `frontend/src/shared-minimal/utils/units.ts`
|
|
|
|
The mobile settings should use the existing unit conversion utilities and persist to the same storage key (`motovaultpro-unit-system`).
|
|
|
|
#### Zustand Store Integration
|
|
**File**: `frontend/src/core/store/index.ts`
|
|
|
|
Extend the existing store to include settings state if needed for cross-component access.
|
|
|
|
## Testing Requirements
|
|
|
|
### Mobile Testing Checklist
|
|
- ✅ Settings screen renders correctly on mobile devices
|
|
- ✅ All sections (Account, Notifications, Appearance, Data, Actions) function properly
|
|
- ✅ Dark mode toggle works and persists
|
|
- ✅ Unit system changes work and persist
|
|
- ✅ Logout functionality works correctly
|
|
- ✅ Account deletion flow works (with confirmation)
|
|
- ✅ Settings persist across app restarts
|
|
- ✅ Navigation to/from settings maintains context
|
|
|
|
### Desktop Compatibility Testing
|
|
- ✅ Changes don't break existing desktop settings
|
|
- ✅ Settings synchronize between mobile and desktop views
|
|
- ✅ Unit system changes reflect in both interfaces
|
|
- ✅ Authentication flows remain consistent
|
|
|
|
### Integration Testing
|
|
- ✅ Settings integrate properly with existing Auth0 authentication
|
|
- ✅ Unit preferences work across all features (vehicles, fuel logs, etc.)
|
|
- ✅ Settings state management doesn't conflict with existing Zustand store
|
|
- ✅ localStorage persistence works correctly
|
|
|
|
## Migration Strategy
|
|
|
|
### Phase 1: Component Creation
|
|
1. Create the mobile settings directory structure
|
|
2. Implement individual settings section components
|
|
3. Create settings hooks for state management
|
|
|
|
### Phase 2: Integration
|
|
1. Replace placeholder in App.tsx
|
|
2. Test mobile settings functionality
|
|
3. Verify persistence and state management
|
|
|
|
### Phase 3: Enhancement
|
|
1. Add any missing features from desktop version
|
|
2. Implement mobile-specific optimizations
|
|
3. Ensure full feature parity
|
|
|
|
## Success Criteria
|
|
|
|
Upon completion, the mobile settings should:
|
|
|
|
1. **Feature Parity**: Match all desktop settings functionality
|
|
2. **Mobile-Optimized**: Use appropriate mobile UI patterns and components
|
|
3. **Persistent**: All settings persist across app restarts
|
|
4. **Integrated**: Work seamlessly with existing authentication and state management
|
|
5. **Tested**: Pass all mobile and desktop compatibility tests
|
|
|
|
This implementation will eliminate the critical mobile settings gap and provide a comprehensive settings experience across all platforms. |