Initial Commit
This commit is contained in:
445
docs/changes/mobile-optimization-v1/03-MOBILE-SETTINGS.md
Normal file
445
docs/changes/mobile-optimization-v1/03-MOBILE-SETTINGS.md
Normal file
@@ -0,0 +1,445 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user