diff --git a/README.md b/README.md index 9fe26d1..96662cf 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,6 @@ EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= EXPO_PUBLIC_FIREBASE_APP_ID= ``` -4. Dans Firebase Console, active `Authentication > Sign-in method > Email/Password` - -5. Crée Firestore dans le même projet Firebase - ## Lancer le projet ```bash diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index e8b2ef5..6333ccc 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -18,6 +18,13 @@ export default function TabLayout() { tabBarIcon: ({ color }) => , }} /> + , + }} + /> ); } diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx new file mode 100644 index 0000000..61b0f7d --- /dev/null +++ b/app/(tabs)/settings.tsx @@ -0,0 +1,165 @@ +import { MaterialCommunityIcons } from '@expo/vector-icons'; +import { Redirect, router } from 'expo-router'; +import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native'; + +import { Avatar } from '@/components/common/Avatar'; +import { Catamaran, FunnelSans } from '@/constants/fonts'; +import { Palette } from '@/constants/palette'; +import { Radius, Spacing } from '@/constants/styles'; +import { useAuth } from '@/providers/AuthProvider'; + +function SettingsRow({ + label, + description, + onPress, +}: { + label: string; + description: string; + onPress: () => void; +}) { + return ( + + + {label} + {description} + + + + ); +} + +export default function SettingsScreen() { + const { initializing, user } = useAuth(); + + if (initializing) { + return ( + + + + ); + } + + if (!user) { + return ; + } + + return ( + + + Settings + Gere ton compte et ton profil. + + + + + + + {user.username || 'Profil'} + {user.email} + + + + + Account + + router.push('/profile')} + /> + + + + ); +} + +const styles = StyleSheet.create({ + centered: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: Palette.cream, + }, + container: { + flex: 1, + paddingHorizontal: Spacing.lg, + paddingTop: Spacing.xl, + gap: Spacing.lg, + backgroundColor: Palette.cream, + }, + header: { + gap: Spacing.xs, + }, + title: { + ...Catamaran.extraBold34, + color: Palette.black, + }, + subtitle: { + ...FunnelSans.regular16, + color: Palette.gray, + }, + previewCard: { + flexDirection: 'row', + alignItems: 'center', + gap: Spacing.md, + padding: Spacing.md, + borderWidth: 1, + borderColor: Palette.cream, + borderRadius: Radius.xl, + backgroundColor: Palette.white, + }, + previewContent: { + flex: 1, + gap: Spacing.xs, + }, + previewTitle: { + ...FunnelSans.medium16, + color: Palette.black, + }, + previewSubtitle: { + ...FunnelSans.medium14, + color: Palette.gray, + }, + section: { + gap: Spacing.sm, + }, + sectionTitle: { + ...FunnelSans.medium12, + color: Palette.gray, + textTransform: 'uppercase', + }, + sectionCard: { + borderWidth: 1, + borderColor: Palette.cream, + borderRadius: Radius.xl, + backgroundColor: Palette.white, + overflow: 'hidden', + }, + row: { + flexDirection: 'row', + alignItems: 'center', + gap: Spacing.md, + paddingHorizontal: Spacing.md, + paddingVertical: Spacing.md, + }, + rowContent: { + flex: 1, + gap: Spacing.xs, + }, + rowLabel: { + ...FunnelSans.medium16, + color: Palette.black, + }, + rowDescription: { + ...FunnelSans.regular16, + color: Palette.gray, + }, +}); diff --git a/app/_layout.tsx b/app/_layout.tsx index ceec77e..af55a8f 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -4,6 +4,7 @@ import { useEffect } from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { AuthProvider } from '@/providers/AuthProvider'; +import { Palette } from '@/constants/palette'; SplashScreen.preventAutoHideAsync(); @@ -34,6 +35,14 @@ export default function RootLayout() { + diff --git a/app/profile.tsx b/app/profile.tsx new file mode 100644 index 0000000..0c304ae --- /dev/null +++ b/app/profile.tsx @@ -0,0 +1,124 @@ +import { Redirect } from 'expo-router'; +import { ScrollView, StyleSheet, Text, View } from 'react-native'; + +import { Avatar } from '@/components/common/Avatar'; +import { Catamaran, FunnelSans } from '@/constants/fonts'; +import { Palette } from '@/constants/palette'; +import { Radius, Spacing } from '@/constants/styles'; +import { useAuth } from '@/providers/AuthProvider'; + +function formatDate(value?: Date | null) { + if (!value) { + return '-'; + } + + return value.toLocaleDateString('fr-FR', { + day: '2-digit', + month: 'long', + year: 'numeric', + }); +} + +function InfoRow({ label, value }: { label: string; value: string }) { + return ( + + {label} + {value} + + ); +} + +export default function ProfileScreen() { + const { initializing, user } = useAuth(); + + if (initializing) { + return ; + } + + if (!user) { + return ; + } + + return ( + + + + + {user.username || 'Profil'} + {user.email} + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + screen: { + flex: 1, + backgroundColor: Palette.cream, + }, + content: { + paddingHorizontal: Spacing.lg, + paddingTop: Spacing.xl, + paddingBottom: Spacing.xxl, + gap: Spacing.lg, + }, + header: { + alignItems: 'center', + gap: Spacing.md, + }, + headerContent: { + alignItems: 'center', + gap: Spacing.xs, + }, + title: { + ...Catamaran.extraBold34, + color: Palette.black, + }, + subtitle: { + ...FunnelSans.regular16, + color: Palette.gray, + }, + card: { + gap: Spacing.md, + padding: Spacing.md, + borderWidth: 1, + borderColor: Palette.cream, + borderRadius: Radius.xl, + backgroundColor: Palette.white, + }, + infoRow: { + gap: Spacing.xs, + paddingBottom: Spacing.sm, + borderBottomWidth: 1, + borderBottomColor: Palette.cream, + }, + infoLabel: { + ...FunnelSans.medium12, + color: Palette.gray, + textTransform: 'uppercase', + }, + infoValue: { + ...FunnelSans.regular16, + color: Palette.black, + }, +}); diff --git a/components/common/Avatar.tsx b/components/common/Avatar.tsx new file mode 100644 index 0000000..f726692 --- /dev/null +++ b/components/common/Avatar.tsx @@ -0,0 +1,104 @@ +import { Image } from 'expo-image'; +import { + StyleSheet, + Text, + View, + type ImageStyle, + type StyleProp, + type ViewStyle, +} from 'react-native'; + +import { Catamaran, FunnelSans } from '@/constants/fonts'; +import { Palette } from '@/constants/palette'; +import { Radius } from '@/constants/styles'; + +type AvatarSize = 'sm' | 'md' | 'lg'; + +type AvatarProps = { + uri?: string | null; + name?: string | null; + size?: AvatarSize; + style?: StyleProp; +}; + +const avatarSizeMap = { + sm: 40, + md: 64, + lg: 104, +} as const; + +function getInitials(name?: string | null) { + if (!name?.trim()) { + return '?'; + } + + const parts = name + .trim() + .split(/\s+/) + .slice(0, 2); + + return parts.map((part) => part[0]?.toUpperCase() ?? '').join('') || '?'; +} + +export function Avatar({ uri, name, size = 'md', style }: AvatarProps) { + const avatarSize = avatarSizeMap[size]; + const initials = getInitials(name); + + if (uri) { + return ( + + ); + } + + return ( + + {initials} + + ); +} + +const styles = StyleSheet.create({ + base: { + overflow: 'hidden', + }, + fallback: { + alignItems: 'center', + justifyContent: 'center', + backgroundColor: Palette.black, + borderWidth: 1, + borderColor: Palette.gray, + }, + text: { + ...FunnelSans.semiBold14, + color: Palette.white, + }, + largeText: { + ...Catamaran.bold18, + color: Palette.white, + }, +}); + +export default Avatar;