Initial commit: WalletTracker app with Firebase integration

- Setup Expo project with TypeScript
- Implement authentication (Login/Signup/Logout)
- Create Dashboard, Transactions, Subscriptions, and Analysis screens
- Add Firebase services (Auth, Firestore, Storage)
- Implement real-time synchronization
- Add charts and analytics
- Create reusable components (Button, InputText, TransactionCard, SubscriptionCard)
- Configure React Navigation with bottom tabs
- Add Firestore security rules
- Create comprehensive documentation (README, FIREBASE_SETUP, TESTING)
This commit is contained in:
2025-10-23 14:36:36 +02:00
parent c10b5ae013
commit 8bde3d4f21
26 changed files with 5622 additions and 17 deletions

View File

@@ -0,0 +1,317 @@
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
RefreshControl,
TouchableOpacity
} from 'react-native';
import { useAuth } from '../hooks/useAuth';
import { transactionService } from '../services/transactionService';
import { Transaction } from '../types';
import { TransactionCard } from '../components/TransactionCard';
export const DashboardScreen = ({ navigation }: any) => {
const { user, logout } = useAuth();
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
if (!user) return;
const unsubscribe = transactionService.subscribeToTransactions(
user.uid,
(newTransactions) => {
setTransactions(newTransactions);
setLoading(false);
setRefreshing(false);
}
);
return () => unsubscribe();
}, [user]);
const onRefresh = () => {
setRefreshing(true);
};
const getCurrentMonthStats = () => {
const now = new Date();
const currentMonth = now.getMonth();
const currentYear = now.getFullYear();
const monthlyTransactions = transactions.filter((t) => {
const transactionDate = new Date(t.date);
return (
transactionDate.getMonth() === currentMonth &&
transactionDate.getFullYear() === currentYear
);
});
const totalIncome = monthlyTransactions
.filter((t) => t.type === 'income')
.reduce((sum, t) => sum + t.amount, 0);
const totalExpenses = monthlyTransactions
.filter((t) => t.type === 'expense')
.reduce((sum, t) => sum + t.amount, 0);
const balance = totalIncome - totalExpenses;
return { totalIncome, totalExpenses, balance };
};
const stats = getCurrentMonthStats();
const recentTransactions = transactions.slice(0, 5);
const getMonthName = () => {
return new Intl.DateTimeFormat('fr-FR', { month: 'long', year: 'numeric' }).format(
new Date()
);
};
return (
<ScrollView
style={styles.container}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
<View style={styles.header}>
<View>
<Text style={styles.greeting}>Bonjour 👋</Text>
<Text style={styles.monthLabel}>{getMonthName()}</Text>
</View>
<TouchableOpacity onPress={logout} style={styles.logoutButton}>
<Text style={styles.logoutText}>Déconnexion</Text>
</TouchableOpacity>
</View>
<View style={styles.balanceCard}>
<Text style={styles.balanceLabel}>Solde du mois</Text>
<Text
style={[
styles.balanceAmount,
stats.balance >= 0 ? styles.positiveBalance : styles.negativeBalance
]}
>
{stats.balance >= 0 ? '+' : ''}{stats.balance.toFixed(2)}
</Text>
<View style={styles.statsRow}>
<View style={styles.statItem}>
<Text style={styles.statLabel}>Revenus</Text>
<Text style={styles.incomeText}>+{stats.totalIncome.toFixed(2)} </Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statLabel}>Dépenses</Text>
<Text style={styles.expenseText}>-{stats.totalExpenses.toFixed(2)} </Text>
</View>
</View>
</View>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Transactions récentes</Text>
{transactions.length > 5 && (
<TouchableOpacity onPress={() => navigation.navigate('Transactions')}>
<Text style={styles.seeAllText}>Voir tout</Text>
</TouchableOpacity>
)}
</View>
{loading ? (
<Text style={styles.emptyText}>Chargement...</Text>
) : recentTransactions.length === 0 ? (
<View style={styles.emptyState}>
<Text style={styles.emptyIcon}>📊</Text>
<Text style={styles.emptyText}>Aucune transaction</Text>
<Text style={styles.emptySubtext}>
Ajoutez votre première transaction pour commencer
</Text>
</View>
) : (
recentTransactions.map((transaction) => (
<TransactionCard key={transaction.id} transaction={transaction} />
))
)}
</View>
<View style={styles.quickActions}>
<TouchableOpacity
style={[styles.actionButton, styles.addExpenseButton]}
onPress={() => navigation.navigate('Transactions', { type: 'expense' })}
>
<Text style={styles.actionIcon}></Text>
<Text style={styles.actionText}>Dépense</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, styles.addIncomeButton]}
onPress={() => navigation.navigate('Transactions', { type: 'income' })}
>
<Text style={styles.actionIcon}></Text>
<Text style={styles.actionText}>Revenu</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F8F9FA'
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 24,
paddingTop: 60
},
greeting: {
fontSize: 28,
fontWeight: 'bold',
color: '#333'
},
monthLabel: {
fontSize: 14,
color: '#666',
marginTop: 4,
textTransform: 'capitalize'
},
logoutButton: {
padding: 8
},
logoutText: {
color: '#4A90E2',
fontSize: 14,
fontWeight: '600'
},
balanceCard: {
backgroundColor: '#4A90E2',
margin: 24,
marginTop: 0,
padding: 24,
borderRadius: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 5
},
balanceLabel: {
fontSize: 14,
color: '#FFF',
opacity: 0.9,
marginBottom: 8
},
balanceAmount: {
fontSize: 36,
fontWeight: 'bold',
marginBottom: 20
},
positiveBalance: {
color: '#FFF'
},
negativeBalance: {
color: '#FFE0E0'
},
statsRow: {
flexDirection: 'row',
justifyContent: 'space-around'
},
statItem: {
flex: 1,
alignItems: 'center'
},
statDivider: {
width: 1,
backgroundColor: '#FFF',
opacity: 0.3
},
statLabel: {
fontSize: 12,
color: '#FFF',
opacity: 0.8,
marginBottom: 4
},
incomeText: {
fontSize: 16,
fontWeight: '600',
color: '#FFF'
},
expenseText: {
fontSize: 16,
fontWeight: '600',
color: '#FFF'
},
section: {
padding: 24,
paddingTop: 0
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#333'
},
seeAllText: {
fontSize: 14,
color: '#4A90E2',
fontWeight: '600'
},
emptyState: {
alignItems: 'center',
paddingVertical: 40
},
emptyIcon: {
fontSize: 48,
marginBottom: 12
},
emptyText: {
fontSize: 16,
color: '#666',
textAlign: 'center'
},
emptySubtext: {
fontSize: 14,
color: '#999',
textAlign: 'center',
marginTop: 8
},
quickActions: {
flexDirection: 'row',
padding: 24,
paddingTop: 0,
gap: 12
},
actionButton: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
borderRadius: 12,
gap: 8
},
addExpenseButton: {
backgroundColor: '#FF6B6B'
},
addIncomeButton: {
backgroundColor: '#52C41A'
},
actionIcon: {
fontSize: 20
},
actionText: {
color: '#FFF',
fontSize: 16,
fontWeight: '600'
}
});