feat(mobile): replace starter template with dashboard-driven app flow

This commit is contained in:
2026-03-07 10:20:00 +00:00
parent d03b22a99f
commit 64684eaae6
34 changed files with 4645 additions and 895 deletions

View File

@@ -1,95 +1,143 @@
import { StyleSheet, TouchableOpacity } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import React from 'react';
import { Pressable, SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useTheme } from '@/context/ThemeContext';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { useApp } from '@/src/app-context';
export default function SettingsScreen() {
const colorScheme = useColorScheme();
const { themeMode, setThemeMode } = useTheme();
const theme = colorScheme ?? 'light';
const { state, actions } = useApp();
const cardStyle = [
styles.card,
{
shadowColor: Colors[theme].border,
borderColor: Colors[theme].border
}
];
useFocusEffect(
React.useCallback(() => {
actions.setPage('settings');
return undefined;
}, [actions]),
);
return (
<ThemedView style={styles.container}>
<ThemedView style={styles.header}>
<ThemedText type="title">Settings</ThemedText>
<ThemedText type="secondary">App preferences</ThemedText>
</ThemedView>
const profileName = state.session?.user?.name || 'User';
const profileEmail = state.session?.user?.email || 'user@example.com';
const profileInitial = (profileName[0] || 'U').toUpperCase();
<ThemedView style={styles.sectionHeader}>
<ThemedText type="subtitle">Appearance</ThemedText>
</ThemedView>
<ThemedView style={cardStyle} variant="card">
<TouchableOpacity
style={styles.item}
onPress={() => setThemeMode('light')}
>
<ThemedText>Light Mode</ThemedText>
{themeMode === 'light' && <IconSymbol name="checkmark" size={20} color={Colors[theme].tint} />}
</TouchableOpacity>
return (
<SafeAreaView style={styles.safe}>
<ScrollView contentContainerStyle={styles.content}>
<Text style={styles.title}>Settings</Text>
<TouchableOpacity
style={styles.item}
onPress={() => setThemeMode('dark')}
>
<ThemedText>Dark Mode</ThemedText>
{themeMode === 'dark' && <IconSymbol name="checkmark" size={20} color={Colors[theme].tint} />}
</TouchableOpacity>
<View style={styles.profileCard}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>{profileInitial}</Text>
</View>
<View>
<Text style={styles.profileName}>{profileName}</Text>
<Text style={styles.profileEmail}>{profileEmail}</Text>
</View>
</View>
<TouchableOpacity
style={[styles.item, styles.lastItem]}
onPress={() => setThemeMode('system')}
>
<ThemedText>System Default</ThemedText>
{themeMode === 'system' && <IconSymbol name="checkmark" size={20} color={Colors[theme].tint} />}
</TouchableOpacity>
</ThemedView>
</ThemedView>
);
<View style={styles.menuCard}>
<Pressable style={styles.menuItem} onPress={actions.runDiagnostics}>
<Text style={styles.menuTitle}>Run Diagnostics</Text>
<Text style={styles.menuArrow}>{'>'}</Text>
</Pressable>
<View style={styles.divider} />
<View style={styles.menuItem}>
<Text style={styles.menuTitle}>Device Information</Text>
<Text style={styles.menuArrow}>{'>'}</Text>
</View>
</View>
<Pressable style={styles.signOutButton} onPress={() => void actions.signOut()}>
<Text style={styles.signOutText}>Sign Out</Text>
</Pressable>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
paddingTop: 80,
},
header: {
marginBottom: 32,
},
sectionHeader: {
marginBottom: 12,
marginTop: 24,
},
card: {
borderRadius: 16,
borderWidth: 1,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 8,
elevation: 2,
overflow: 'hidden',
},
item: {
padding: 16,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'rgba(0,0,0,0.05)',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
lastItem: {
borderBottomWidth: 0,
},
safe: {
flex: 1,
backgroundColor: '#0a0a0c',
},
content: {
padding: 14,
gap: 14,
},
title: {
color: '#f9fafb',
fontSize: 22,
fontWeight: '700',
},
profileCard: {
borderRadius: 16,
padding: 14,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.08)',
backgroundColor: '#111218',
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
avatar: {
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: '#1d4ed8',
alignItems: 'center',
justifyContent: 'center',
},
avatarText: {
color: '#f9fafb',
fontSize: 24,
fontWeight: '700',
},
profileName: {
color: '#f3f4f6',
fontSize: 17,
fontWeight: '600',
},
profileEmail: {
color: '#9ca3af',
marginTop: 3,
fontSize: 13,
},
menuCard: {
borderRadius: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.08)',
backgroundColor: '#111218',
overflow: 'hidden',
},
menuItem: {
minHeight: 52,
paddingHorizontal: 14,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
menuTitle: {
color: '#e5e7eb',
fontSize: 14,
fontWeight: '500',
},
menuArrow: {
color: '#6b7280',
fontSize: 14,
},
divider: {
height: 1,
backgroundColor: 'rgba(255,255,255,0.08)',
},
signOutButton: {
height: 48,
borderRadius: 12,
borderWidth: 1,
borderColor: 'rgba(248,113,113,0.5)',
alignItems: 'center',
justifyContent: 'center',
},
signOutText: {
color: '#fca5a5',
fontWeight: '700',
fontSize: 14,
},
});