Settings Screens
Paramètres de l'application : profil, sécurité, notifications, apparence, langue, appareils connectés et confidentialité.
Overview
Le module Settings regroupe 10+ écrans de configuration organisés par catégorie. Chaque écran utilise des composants de formulaire standardisés (toggles, sliders, pickers) et sauvegarde les préférences localement et sur le serveur pour synchronisation multi-appareils.
Profil
Photo, nom, email, téléphone
Sécurité
PIN, biométrie, 2FA, sessions
Notifications
Push, email, catégories
Apparence
Thème, police, mode sombre
Langue
16+ langues, RTL support
Appareils
Sessions actives, déconnexion
Devise
Devise d'affichage préférée
Confidentialité
Données, export, suppression
Synchronisation
Profile Edit
L'écran d'édition du profil permet de modifier la photo d'avatar, le nom, l'email, le téléphone et l'adresse. Les modifications d'email et de téléphone nécessitent une vérification par code OTP.
Upload avatar
Photo ou galérie, crop circulaire
Nom complet
Prénom et nom de famille
Vérification OTP requise au changement
Téléphone
Vérification SMS au changement
export default function ProfileEditScreen() {
const { user } = useAuthStore();
const updateMutation = useUpdateProfile();
const [avatar, setAvatar] = useState<string | null>(user?.avatar || null);
const {
control,
handleSubmit,
formState: { errors, isDirty },
} = useForm({
defaultValues: {
firstName: user?.firstName || '',
lastName: user?.lastName || '',
email: user?.email || '',
phone: user?.phone || '',
},
});
const handleAvatarPick = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
const uploadResult = await uploadAvatar(result.assets[0].uri);
setAvatar(uploadResult.url);
}
};
const onSubmit = async (data: ProfileFormData) => {
try {
// Check if email/phone changed (requires OTP)
if (data.email !== user?.email) {
router.push({
pathname: '/settings/profile/verify-email',
params: { newEmail: data.email },
});
return;
}
if (data.phone !== user?.phone) {
router.push({
pathname: '/settings/profile/verify-phone',
params: { newPhone: data.phone },
});
return;
}
await updateMutation.mutateAsync({ ...data, avatar });
Alert.alert('Succès', 'Profil mis à jour');
} catch (error) {
Alert.alert('Erreur', 'Impossible de mettre à jour le profil.');
}
};
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* Avatar */}
<TouchableOpacity
style={styles.avatarContainer}
onPress={handleAvatarPick}
>
<Avatar source={avatar} name={user?.name} size={100} />
<View style={styles.cameraIcon}>
<Camera size={16} color="#fff" />
</View>
</TouchableOpacity>
{/* Form Fields */}
<View style={styles.form}>
<FormField
control={control}
name="firstName"
label="Prénom"
rules={{ required: 'Prénom requis' }}
error={errors.firstName}
/>
<FormField
control={control}
name="lastName"
label="Nom"
rules={{ required: 'Nom requis' }}
error={errors.lastName}
/>
<FormField
control={control}
name="email"
label="Email"
keyboardType="email-address"
rules={{
required: 'Email requis',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Email invalide',
},
}}
error={errors.email}
/>
<FormField
control={control}
name="phone"
label="Téléphone"
keyboardType="phone-pad"
error={errors.phone}
/>
</View>
</ScrollView>
<Button
title="Enregistrer"
onPress={handleSubmit(onSubmit)}
loading={updateMutation.isPending}
disabled={!isDirty && !avatar}
/>
</SafeAreaView>
);
}Security Settings
Les paramètres de sécurité incluent le changement de PIN, la gestion biométrique (Face ID / Touch ID), l'authentification à deux facteurs (2FA) et la gestion des sessions actives.
Changer le PIN
Modifier votre code PIN à 6 chiffres
Biométrie
Face ID / Touch ID pour déverrouillage et paiements
2FA (TOTP)
Google Authenticator ou Authy
Gestion des sessions
Voir et révoquer les sessions actives
export default function SecuritySettingsScreen() {
const { data: securitySettings } = useSecuritySettings();
const updateMutation = useUpdateSecuritySettings();
const [biometricEnabled, setBiometricEnabled] = useState(
securitySettings?.biometricEnabled ?? false
);
const [biometricForPayments, setBiometricForPayments] = useState(
securitySettings?.biometricForPayments ?? false
);
const [twoFactorEnabled, setTwoFactorEnabled] = useState(
securitySettings?.twoFactorEnabled ?? false
);
const handleToggleBiometric = async (enabled: boolean) => {
if (enabled) {
// Check biometric availability
const compatible = await LocalAuthentication.hasHardwareAsync();
const enrolled = await LocalAuthentication.isEnrolledAsync();
if (!compatible || !enrolled) {
Alert.alert(
'Non disponible',
'La biométrie n\'est pas configurée sur cet appareil.'
);
return;
}
// Test biometric
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Activer la biométrie',
});
if (!result.success) return;
}
setBiometricEnabled(enabled);
await updateMutation.mutateAsync({ biometricEnabled: enabled });
};
const handleToggle2FA = () => {
if (!twoFactorEnabled) {
// Navigate to 2FA setup flow
router.push('/settings/security/setup-2fa');
} else {
// Confirm disable with PIN
Alert.alert(
'Désactiver le 2FA',
'Cette action réduit la sécurité de votre compte.',
[
{ text: 'Annuler', style: 'cancel' },
{
text: 'Désactiver',
style: 'destructive',
onPress: () => router.push('/settings/security/disable-2fa'),
},
]
);
}
};
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* PIN */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Code PIN</Text>
<TouchableOpacity
style={styles.settingRow}
onPress={() => router.push('/settings/security/change-pin')}
>
<Key size={20} color="#64748b" />
<Text style={styles.settingLabel}>Changer le PIN</Text>
<ChevronRight size={20} color="#64748b" />
</TouchableOpacity>
</View>
{/* Biometric */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Biométrie</Text>
<SettingToggle
icon={<Fingerprint size={20} />}
label="Déverrouillage biométrique"
description="Utiliser Face ID / Touch ID pour ouvrir l'app"
value={biometricEnabled}
onChange={handleToggleBiometric}
/>
<SettingToggle
icon={<Lock size={20} />}
label="Paiements biométriques"
description="Confirmer les paiements par biométrie"
value={biometricForPayments}
onChange={(v) => {
setBiometricForPayments(v);
updateMutation.mutate({ biometricForPayments: v });
}}
disabled={!biometricEnabled}
/>
</View>
{/* 2FA */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Authentification 2FA</Text>
<SettingToggle
icon={<Shield size={20} />}
label="Double authentification"
description="TOTP via Google Authenticator ou Authy"
value={twoFactorEnabled}
onChange={handleToggle2FA}
/>
</View>
{/* Sessions */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Sessions actives</Text>
<TouchableOpacity
style={styles.settingRow}
onPress={() => router.push('/settings/security/sessions')}
>
<Smartphone size={20} color="#64748b" />
<Text style={styles.settingLabel}>Gérer les sessions</Text>
<View style={styles.sessionCount}>
<Text style={styles.sessionCountText}>
{securitySettings?.activeSessions || 1}
</Text>
</View>
<ChevronRight size={20} color="#64748b" />
</TouchableOpacity>
</View>
</ScrollView>
</SafeAreaView>
);
}Sécurité critique
Notification Preferences
L'utilisateur peut configurer ses préférences de notifications push par catégorie : transactions, sécurité, marketing et DeFi.
Paiements, virements, cashback
ONConnexions, alertes, 2FA
ONOffres, promotions, news
OFFStaking, governance, rewards
ONinterface NotificationPreferences {
transactions: boolean;
security: boolean;
marketing: boolean;
defi: boolean;
priceAlerts: boolean;
weeklyReport: boolean;
emailNotifications: boolean;
soundEnabled: boolean;
vibrationEnabled: boolean;
}
export default function NotificationSettingsScreen() {
const { data: prefs } = useNotificationPreferences();
const updateMutation = useUpdateNotificationPreferences();
const [preferences, setPreferences] = useState<NotificationPreferences>(
prefs || {
transactions: true,
security: true,
marketing: false,
defi: true,
priceAlerts: true,
weeklyReport: true,
emailNotifications: false,
soundEnabled: true,
vibrationEnabled: true,
}
);
const handleToggle = (key: keyof NotificationPreferences, value: boolean) => {
// Security notifications cannot be disabled
if (key === 'security' && !value) {
Alert.alert(
'Non autorisé',
'Les notifications de sécurité ne peuvent pas être désactivées.'
);
return;
}
const updated = { ...preferences, [key]: value };
setPreferences(updated);
updateMutation.mutate(updated);
};
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* Push Categories */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Catégories push</Text>
<SettingToggle
label="Transactions"
description="Paiements reçus, envoyés, cashback"
value={preferences.transactions}
onChange={(v) => handleToggle('transactions', v)}
/>
<SettingToggle
label="Sécurité"
description="Connexions, alertes, codes 2FA"
value={preferences.security}
onChange={(v) => handleToggle('security', v)}
locked
/>
<SettingToggle
label="Marketing"
description="Offres spéciales, promotions"
value={preferences.marketing}
onChange={(v) => handleToggle('marketing', v)}
/>
<SettingToggle
label="DeFi"
description="Staking rewards, governance votes"
value={preferences.defi}
onChange={(v) => handleToggle('defi', v)}
/>
<SettingToggle
label="Alertes de prix"
description="Variations de prix crypto"
value={preferences.priceAlerts}
onChange={(v) => handleToggle('priceAlerts', v)}
/>
</View>
{/* Email & Reports */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Email</Text>
<SettingToggle
label="Notifications par email"
description="Recevoir un email pour chaque transaction"
value={preferences.emailNotifications}
onChange={(v) => handleToggle('emailNotifications', v)}
/>
<SettingToggle
label="Rapport hebdomadaire"
description="Résumé de vos finances chaque lundi"
value={preferences.weeklyReport}
onChange={(v) => handleToggle('weeklyReport', v)}
/>
</View>
{/* Sound & Vibration */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Son et vibration</Text>
<SettingToggle
icon={preferences.soundEnabled ? <Volume2 size={20} /> : <VolumeX size={20} />}
label="Son"
value={preferences.soundEnabled}
onChange={(v) => handleToggle('soundEnabled', v)}
/>
<SettingToggle
label="Vibration"
value={preferences.vibrationEnabled}
onChange={(v) => handleToggle('vibrationEnabled', v)}
/>
</View>
</ScrollView>
</SafeAreaView>
);
}Appearance
L'écran d'apparence permet de basculer entre les thèmes clair, sombre et système, et d'ajuster la taille de police.
Light
Thème clair
Dark
Thème sombre
System
Automatique
type ThemeMode = 'light' | 'dark' | 'system';
type FontSize = 'small' | 'medium' | 'large';
export default function AppearanceSettingsScreen() {
const { theme, setTheme } = useThemeStore();
const { fontSize, setFontSize } = usePreferencesStore();
const themes: { key: ThemeMode; label: string; icon: React.ReactNode }[] = [
{ key: 'light', label: 'Clair', icon: <Sun size={24} /> },
{ key: 'dark', label: 'Sombre', icon: <Moon size={24} /> },
{ key: 'system', label: 'Système', icon: <Monitor size={24} /> },
];
const fontSizes: { key: FontSize; label: string; preview: number }[] = [
{ key: 'small', label: 'Petit', preview: 14 },
{ key: 'medium', label: 'Normal', preview: 16 },
{ key: 'large', label: 'Grand', preview: 20 },
];
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* Theme Selection */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Thème</Text>
<View style={styles.themeGrid}>
{themes.map((t) => (
<TouchableOpacity
key={t.key}
style={[
styles.themeCard,
theme === t.key && styles.selectedTheme,
]}
onPress={() => setTheme(t.key)}
>
{t.icon}
<Text style={styles.themeLabel}>{t.label}</Text>
{theme === t.key && (
<View style={styles.checkmark}>
<CheckCircle size={20} color="#06b6d4" />
</View>
)}
</TouchableOpacity>
))}
</View>
</View>
{/* Font Size */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Taille de police</Text>
{fontSizes.map((fs) => (
<TouchableOpacity
key={fs.key}
style={[
styles.fontSizeOption,
fontSize === fs.key && styles.selectedFontSize,
]}
onPress={() => setFontSize(fs.key)}
>
<Text style={[styles.fontPreview, { fontSize: fs.preview }]}>
Aa
</Text>
<Text style={styles.fontLabel}>{fs.label}</Text>
{fontSize === fs.key && (
<CheckCircle size={20} color="#06b6d4" />
)}
</TouchableOpacity>
))}
</View>
{/* Preview */}
<View style={styles.previewSection}>
<Text style={styles.sectionTitle}>Aperçu</Text>
<View style={styles.previewCard}>
<Text style={[styles.previewTitle, { fontSize: fontSizes.find(f => f.key === fontSize)?.preview || 16 }]}>
Solde disponible
</Text>
<Text style={styles.previewAmount}>1 234,56 €</Text>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}Language
YaniPay supporte 16+ langues avec détection automatique de la locale de l'appareil. Le support RTL (Right-to-Left) est inclus pour l'arabe et l'hébreu.
RTL Support
import { I18nManager } from 'react-native';
import * as Updates from 'expo-updates';
const LANGUAGES = [
{ code: 'fr', name: 'Français', nativeName: 'Français', rtl: false },
{ code: 'en', name: 'English', nativeName: 'English', rtl: false },
{ code: 'es', name: 'Spanish', nativeName: 'Español', rtl: false },
{ code: 'de', name: 'German', nativeName: 'Deutsch', rtl: false },
{ code: 'it', name: 'Italian', nativeName: 'Italiano', rtl: false },
{ code: 'pt', name: 'Portuguese', nativeName: 'Português', rtl: false },
{ code: 'nl', name: 'Dutch', nativeName: 'Nederlands', rtl: false },
{ code: 'pl', name: 'Polish', nativeName: 'Polski', rtl: false },
{ code: 'ru', name: 'Russian', nativeName: 'Русский', rtl: false },
{ code: 'zh', name: 'Chinese', nativeName: '中文', rtl: false },
{ code: 'ja', name: 'Japanese', nativeName: '日本語', rtl: false },
{ code: 'ko', name: 'Korean', nativeName: '한국어', rtl: false },
{ code: 'ar', name: 'Arabic', nativeName: 'العربية', rtl: true },
{ code: 'he', name: 'Hebrew', nativeName: 'עברית', rtl: true },
{ code: 'tr', name: 'Turkish', nativeName: 'Türkçe', rtl: false },
{ code: 'hi', name: 'Hindi', nativeName: 'हिन्दी', rtl: false },
];
export default function LanguageSettingsScreen() {
const { locale, setLocale } = useLocaleStore();
const [searchQuery, setSearchQuery] = useState('');
const filteredLanguages = useMemo(() => {
if (!searchQuery) return LANGUAGES;
const q = searchQuery.toLowerCase();
return LANGUAGES.filter(
(l) =>
l.name.toLowerCase().includes(q) ||
l.nativeName.toLowerCase().includes(q) ||
l.code.includes(q)
);
}, [searchQuery]);
const handleLanguageChange = async (lang: typeof LANGUAGES[0]) => {
const needsRTLChange = lang.rtl !== I18nManager.isRTL;
setLocale(lang.code);
i18n.changeLanguage(lang.code);
if (needsRTLChange) {
I18nManager.forceRTL(lang.rtl);
// Requires app restart for RTL changes
Alert.alert(
'Redémarrage requis',
'L\'application doit redémarrer pour appliquer le changement de direction.',
[
{ text: 'Plus tard', style: 'cancel' },
{
text: 'Redémarrer',
onPress: () => Updates.reloadAsync(),
},
]
);
}
};
return (
<SafeAreaView style={styles.container}>
{/* Search */}
<View style={styles.searchContainer}>
<Search size={20} color="#64748b" />
<TextInput
style={styles.searchInput}
value={searchQuery}
onChangeText={setSearchQuery}
placeholder="Rechercher une langue..."
/>
</View>
{/* Language List */}
<FlatList
data={filteredLanguages}
keyExtractor={(item) => item.code}
renderItem={({ item }) => (
<TouchableOpacity
style={[
styles.langItem,
locale === item.code && styles.selectedLang,
]}
onPress={() => handleLanguageChange(item)}
>
<View>
<Text style={styles.langName}>{item.nativeName}</Text>
<Text style={styles.langSubname}>{item.name}</Text>
</View>
{item.rtl && (
<View style={styles.rtlBadge}>
<Text style={styles.rtlText}>RTL</Text>
</View>
)}
{locale === item.code && (
<CheckCircle size={20} color="#06b6d4" />
)}
</TouchableOpacity>
)}
/>
</SafeAreaView>
);
}Linked Devices
L'écran des appareils connectés affiche toutes les sessions actives avec les informations de l'appareil, la localisation et la date de dernière activité. L'utilisateur peut révoquer une session à distance.
interface DeviceSession {
id: string;
deviceName: string;
deviceType: 'ios' | 'android' | 'web';
browser?: string;
os: string;
location: string;
lastActive: Date;
isCurrent: boolean;
ipAddress: string;
}
export default function LinkedDevicesScreen() {
const { data: sessions } = useDeviceSessions();
const revokeMutation = useRevokeSession();
const revokeAllMutation = useRevokeAllSessions();
const handleRevoke = (session: DeviceSession) => {
Alert.alert(
'Révoquer la session',
`Déconnecter ${session.deviceName} ?`,
[
{ text: 'Annuler', style: 'cancel' },
{
text: 'Déconnecter',
style: 'destructive',
onPress: () => revokeMutation.mutate(session.id),
},
]
);
};
const handleRevokeAll = () => {
Alert.alert(
'Déconnecter tous les appareils',
'Tous les appareils sauf celui-ci seront déconnectés.',
[
{ text: 'Annuler', style: 'cancel' },
{
text: 'Tout déconnecter',
style: 'destructive',
onPress: () => revokeAllMutation.mutate(),
},
]
);
};
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* Current Device */}
<View style={styles.currentDevice}>
<Smartphone size={32} color="#06b6d4" />
<Text style={styles.currentLabel}>Cet appareil</Text>
<Text style={styles.currentName}>
{sessions?.find((s) => s.isCurrent)?.deviceName}
</Text>
</View>
{/* Other Sessions */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Autres sessions</Text>
{sessions && sessions.filter((s) => !s.isCurrent).length > 0 && (
<TouchableOpacity onPress={handleRevokeAll}>
<Text style={styles.revokeAllText}>Tout révoquer</Text>
</TouchableOpacity>
)}
</View>
{sessions
?.filter((s) => !s.isCurrent)
.map((session) => (
<View key={session.id} style={styles.sessionCard}>
<View style={styles.deviceIcon}>
{session.deviceType === 'web' ? (
<Monitor size={24} color="#64748b" />
) : (
<Smartphone size={24} color="#64748b" />
)}
</View>
<View style={styles.sessionInfo}>
<Text style={styles.deviceName}>{session.deviceName}</Text>
<Text style={styles.sessionMeta}>
{session.os} • {session.location}
</Text>
<Text style={styles.lastActive}>
Dernière activité : {formatRelativeDate(session.lastActive)}
</Text>
</View>
<TouchableOpacity
style={styles.revokeButton}
onPress={() => handleRevoke(session)}
>
<LogOut size={18} color="#ef4444" />
</TouchableOpacity>
</View>
))}
</View>
</ScrollView>
</SafeAreaView>
);
}Privacy & Data
Les paramètres de confidentialité permettent de contrôler le partage de données, d'exporter toutes les données personnelles (RGPD) et de supprimer définitivement le compte.
Paramètres de confidentialité
Contrôle de la visibilité du profil et partage analytics
Export de données (RGPD)
Télécharger toutes vos données au format JSON/CSV
Suppression de compte
Supprimer définitivement votre compte et toutes vos données
Suppression de compte
export default function PrivacySettingsScreen() {
const { data: privacySettings } = usePrivacySettings();
const updateMutation = useUpdatePrivacySettings();
const exportMutation = useExportData();
const deleteMutation = useDeleteAccount();
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* Privacy Toggles */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidentialité</Text>
<SettingToggle
label="Profil public"
description="Permettre aux autres utilisateurs de trouver votre profil"
value={privacySettings?.publicProfile ?? false}
onChange={(v) => updateMutation.mutate({ publicProfile: v })}
/>
<SettingToggle
label="Analytics"
description="Partager des données anonymes pour améliorer l'app"
value={privacySettings?.analyticsEnabled ?? true}
onChange={(v) => updateMutation.mutate({ analyticsEnabled: v })}
/>
<SettingToggle
label="Historique de recherche"
description="Sauvegarder vos recherches récentes"
value={privacySettings?.searchHistory ?? true}
onChange={(v) => updateMutation.mutate({ searchHistory: v })}
/>
</View>
{/* Data Export */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Mes données</Text>
<TouchableOpacity
style={styles.actionCard}
onPress={() => {
Alert.alert(
'Export des données',
'Vous recevrez un email avec un lien de téléchargement sous 24h.',
[
{ text: 'Annuler', style: 'cancel' },
{
text: 'Exporter',
onPress: () => exportMutation.mutate(),
},
]
);
}}
>
<Download size={24} color="#10b981" />
<View style={styles.actionInfo}>
<Text style={styles.actionTitle}>Exporter mes données</Text>
<Text style={styles.actionDesc}>
Télécharger toutes vos données (RGPD)
</Text>
</View>
</TouchableOpacity>
{/* Terms and Policy */}
<TouchableOpacity
style={styles.actionCard}
onPress={() => router.push('/legal/privacy-policy')}
>
<FileText size={24} color="#64748b" />
<View style={styles.actionInfo}>
<Text style={styles.actionTitle}>Politique de confidentialité</Text>
<Text style={styles.actionDesc}>
Lire notre politique de traitement des données
</Text>
</View>
</TouchableOpacity>
</View>
{/* Danger Zone */}
<View style={styles.dangerSection}>
<Text style={styles.dangerTitle}>Zone de danger</Text>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => {
Alert.alert(
'Supprimer le compte',
'Cette action est irréversible. Votre compte sera désactivé immédiatement et supprimé définitivement après 30 jours.',
[
{ text: 'Annuler', style: 'cancel' },
{
text: 'Je comprends, supprimer',
style: 'destructive',
onPress: () => router.push('/settings/privacy/delete-confirmation'),
},
]
);
}}
>
<Trash2 size={20} color="#ef4444" />
<Text style={styles.deleteText}>Supprimer mon compte</Text>
</TouchableOpacity>
</View>
</ScrollView>
</SafeAreaView>
);
}Settings Routes
app/
└── settings/
├── _layout.tsx # Settings stack layout
├── index.tsx # Settings main menu
├── profile/
│ ├── index.tsx # Profile edit
│ ├── verify-email.tsx # Email change OTP
│ └── verify-phone.tsx # Phone change OTP
├── security/
│ ├── index.tsx # Security settings
│ ├── change-pin.tsx # Change PIN flow
│ ├── setup-2fa.tsx # 2FA setup (QR code)
│ ├── disable-2fa.tsx # 2FA disable flow
│ └── sessions.tsx # Active sessions list
├── notifications/
│ └── index.tsx # Notification preferences
├── appearance/
│ └── index.tsx # Theme & font settings
├── language/
│ └── index.tsx # Language selector
├── currency/
│ └── index.tsx # Currency settings
├── devices/
│ └── index.tsx # Linked devices
└── privacy/
├── index.tsx # Privacy settings
├── export.tsx # Data export
└── delete-confirmation.tsx # Account deletion