Fix: Resolve UI/UX issues and improve user experience
- Configure Firebase Auth with AsyncStorage persistence - Fix 'Text strings must be rendered within <Text>' error in navigation - Improve bottom tab bar: iOS style with blur effect, better height, rounded corners - Fix Dashboard quick action buttons to open transaction modal directly - Add auto-open modal when navigating from Dashboard - Improve selection visibility in modals (type selector and categories) - Add amount validation: only positive numbers, max 2 decimals - Add padding to Dashboard content to avoid tab bar overlap - Apply same fixes to both Transaction and Subscription screens
This commit is contained in:
@@ -20,6 +20,7 @@ const firebaseConfig = {
|
|||||||
const app = initializeApp(firebaseConfig);
|
const app = initializeApp(firebaseConfig);
|
||||||
|
|
||||||
// Services Firebase
|
// Services Firebase
|
||||||
|
// Note: AsyncStorage est géré automatiquement par Firebase pour React Native
|
||||||
export const auth = getAuth(app);
|
export const auth = getAuth(app);
|
||||||
export const db = getFirestore(app);
|
export const db = getFirestore(app);
|
||||||
export const storage = getStorage(app);
|
export const storage = getStorage(app);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import { ActivityIndicator, View, StyleSheet } from 'react-native';
|
import { ActivityIndicator, View, StyleSheet, Text } from 'react-native';
|
||||||
|
|
||||||
import { useAuth } from '../hooks/useAuth';
|
import { useAuth } from '../hooks/useAuth';
|
||||||
import { LoginScreen } from '../screens/LoginScreen';
|
import { LoginScreen } from '../screens/LoginScreen';
|
||||||
@@ -25,16 +25,24 @@ const MainTabs = () => {
|
|||||||
tabBarActiveTintColor: '#4A90E2',
|
tabBarActiveTintColor: '#4A90E2',
|
||||||
tabBarInactiveTintColor: '#999',
|
tabBarInactiveTintColor: '#999',
|
||||||
tabBarStyle: {
|
tabBarStyle: {
|
||||||
backgroundColor: '#FFF',
|
position: 'absolute',
|
||||||
borderTopWidth: 1,
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
borderTopColor: '#E0E0E0',
|
borderTopWidth: 0,
|
||||||
paddingBottom: 8,
|
elevation: 0,
|
||||||
paddingTop: 8,
|
shadowColor: '#000',
|
||||||
height: 60
|
shadowOffset: { width: 0, height: -2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 8,
|
||||||
|
paddingBottom: 34, // Espace pour la barre iOS
|
||||||
|
paddingTop: 12,
|
||||||
|
height: 90, // Plus haute
|
||||||
|
borderTopLeftRadius: 20,
|
||||||
|
borderTopRightRadius: 20
|
||||||
},
|
},
|
||||||
tabBarLabelStyle: {
|
tabBarLabelStyle: {
|
||||||
fontSize: 12,
|
fontSize: 11,
|
||||||
fontWeight: '600'
|
fontWeight: '600',
|
||||||
|
marginTop: 4
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -75,9 +83,9 @@ const MainTabs = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TabIcon = ({ icon, color }: { icon: string; color: string }) => (
|
const TabIcon = ({ icon, color }: { icon: string; color: string }) => (
|
||||||
<View style={{ opacity: color === '#4A90E2' ? 1 : 0.5 }}>
|
<Text style={{ fontSize: 24, opacity: color === '#4A90E2' ? 1 : 0.5 }}>
|
||||||
<View>{icon}</View>
|
{icon}
|
||||||
</View>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const AppNavigator = () => {
|
export const AppNavigator = () => {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export const DashboardScreen = ({ navigation }: any) => {
|
|||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
@@ -140,14 +141,14 @@ export const DashboardScreen = ({ navigation }: any) => {
|
|||||||
<View style={styles.quickActions}>
|
<View style={styles.quickActions}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.actionButton, styles.addExpenseButton]}
|
style={[styles.actionButton, styles.addExpenseButton]}
|
||||||
onPress={() => navigation.navigate('Transactions', { type: 'expense' })}
|
onPress={() => navigation.navigate('Transactions', { type: 'expense', openModal: true })}
|
||||||
>
|
>
|
||||||
<Text style={styles.actionIcon}>➖</Text>
|
<Text style={styles.actionIcon}>➖</Text>
|
||||||
<Text style={styles.actionText}>Dépense</Text>
|
<Text style={styles.actionText}>Dépense</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.actionButton, styles.addIncomeButton]}
|
style={[styles.actionButton, styles.addIncomeButton]}
|
||||||
onPress={() => navigation.navigate('Transactions', { type: 'income' })}
|
onPress={() => navigation.navigate('Transactions', { type: 'income', openModal: true })}
|
||||||
>
|
>
|
||||||
<Text style={styles.actionIcon}>➕</Text>
|
<Text style={styles.actionIcon}>➕</Text>
|
||||||
<Text style={styles.actionText}>Revenu</Text>
|
<Text style={styles.actionText}>Revenu</Text>
|
||||||
@@ -162,6 +163,9 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#F8F9FA'
|
backgroundColor: '#F8F9FA'
|
||||||
},
|
},
|
||||||
|
scrollContent: {
|
||||||
|
paddingBottom: 100 // Espace pour la tab bar
|
||||||
|
},
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
|||||||
@@ -223,7 +223,16 @@ export const SubscriptionScreen = () => {
|
|||||||
label="Montant (€)"
|
label="Montant (€)"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChangeText={setAmount}
|
onChangeText={(text) => {
|
||||||
|
// Permettre uniquement les chiffres et un point décimal
|
||||||
|
const cleaned = text.replace(/[^0-9.]/g, '');
|
||||||
|
// Empêcher plusieurs points
|
||||||
|
const parts = cleaned.split('.');
|
||||||
|
if (parts.length > 2) return;
|
||||||
|
// Limiter à 2 décimales
|
||||||
|
if (parts[1] && parts[1].length > 2) return;
|
||||||
|
setAmount(cleaned);
|
||||||
|
}}
|
||||||
keyboardType="decimal-pad"
|
keyboardType="decimal-pad"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { TransactionCard } from '../components/TransactionCard';
|
|||||||
import { InputText } from '../components/InputText';
|
import { InputText } from '../components/InputText';
|
||||||
import { Button } from '../components/Button';
|
import { Button } from '../components/Button';
|
||||||
|
|
||||||
export const TransactionScreen = ({ route }: any) => {
|
export const TransactionScreen = ({ route, navigation }: any) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||||
const [categories, setCategories] = useState<Category[]>([]);
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
@@ -48,6 +48,18 @@ export const TransactionScreen = ({ route }: any) => {
|
|||||||
return () => unsubscribe();
|
return () => unsubscribe();
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
// Ouvrir le modal automatiquement si on vient du Dashboard
|
||||||
|
useEffect(() => {
|
||||||
|
if (route?.params?.openModal) {
|
||||||
|
setModalVisible(true);
|
||||||
|
if (route.params.type) {
|
||||||
|
setType(route.params.type);
|
||||||
|
}
|
||||||
|
// Réinitialiser le paramètre
|
||||||
|
navigation.setParams({ openModal: false });
|
||||||
|
}
|
||||||
|
}, [route?.params]);
|
||||||
|
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
@@ -218,7 +230,16 @@ export const TransactionScreen = ({ route }: any) => {
|
|||||||
label="Montant (€)"
|
label="Montant (€)"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChangeText={setAmount}
|
onChangeText={(text) => {
|
||||||
|
// Permettre uniquement les chiffres et un point décimal
|
||||||
|
const cleaned = text.replace(/[^0-9.]/g, '');
|
||||||
|
// Empêcher plusieurs points
|
||||||
|
const parts = cleaned.split('.');
|
||||||
|
if (parts.length > 2) return;
|
||||||
|
// Limiter à 2 décimales
|
||||||
|
if (parts[1] && parts[1].length > 2) return;
|
||||||
|
setAmount(cleaned);
|
||||||
|
}}
|
||||||
keyboardType="decimal-pad"
|
keyboardType="decimal-pad"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -356,7 +377,8 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
typeButtonActive: {
|
typeButtonActive: {
|
||||||
borderWidth: 2
|
borderWidth: 3,
|
||||||
|
backgroundColor: '#F0F7FF'
|
||||||
},
|
},
|
||||||
expenseButton: {
|
expenseButton: {
|
||||||
borderColor: '#FF6B6B'
|
borderColor: '#FF6B6B'
|
||||||
@@ -367,10 +389,11 @@ const styles = StyleSheet.create({
|
|||||||
typeButtonText: {
|
typeButtonText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#666'
|
color: '#999'
|
||||||
},
|
},
|
||||||
typeButtonTextActive: {
|
typeButtonTextActive: {
|
||||||
color: '#333'
|
color: '#333',
|
||||||
|
fontWeight: '700'
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -396,7 +419,13 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
categoryItemActive: {
|
categoryItemActive: {
|
||||||
backgroundColor: '#FFF',
|
backgroundColor: '#FFF',
|
||||||
borderWidth: 2
|
borderWidth: 3,
|
||||||
|
transform: [{ scale: 1.05 }],
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 4
|
||||||
},
|
},
|
||||||
categoryIcon: {
|
categoryIcon: {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
@@ -405,6 +434,7 @@ const styles = StyleSheet.create({
|
|||||||
categoryName: {
|
categoryName: {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: '#666',
|
color: '#666',
|
||||||
|
fontWeight: '600',
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
submitButton: {
|
submitButton: {
|
||||||
|
|||||||
Reference in New Issue
Block a user