diff --git a/app.json b/app.json index e6895e9..3f3eb56 100644 --- a/app.json +++ b/app.json @@ -38,7 +38,8 @@ } ], "expo-image", - "expo-font" + "expo-font", + "expo-secure-store" ], "experiments": { "typedRoutes": true, diff --git a/package-lock.json b/package-lock.json index 8ac4890..09798bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "expo-linear-gradient": "~56.0.4", "expo-linking": "~56.0.12", "expo-router": "~56.2.7", + "expo-secure-store": "~56.0.4", "expo-splash-screen": "~56.0.10", "expo-status-bar": "~56.0.4", "expo-symbols": "~56.0.5", @@ -5124,6 +5125,15 @@ } } }, + "node_modules/expo-secure-store": { + "version": "56.0.4", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-56.0.4.tgz", + "integrity": "sha512-hjEi/gmpdFFJ9lYbdp3k3p/WchV7Gi0Qt8jt/m/0WJadqQrskafHAlDxbZkII1cN3Yd7zp9Lvkeq3UfGhSwirQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-server": { "version": "56.0.4", "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-56.0.4.tgz", diff --git a/package.json b/package.json index c53c047..4f0a2e4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "expo-linear-gradient": "~56.0.4", "expo-linking": "~56.0.12", "expo-router": "~56.2.7", + "expo-secure-store": "~56.0.4", "expo-splash-screen": "~56.0.10", "expo-status-bar": "~56.0.4", "expo-symbols": "~56.0.5", diff --git a/src/app/(auth)/_layout.tsx b/src/app/(auth)/_layout.tsx new file mode 100644 index 0000000..c99d790 --- /dev/null +++ b/src/app/(auth)/_layout.tsx @@ -0,0 +1,13 @@ +import { useAuth } from '@/stores/authStore'; +import { Redirect, Stack } from 'expo-router'; + +export default function AuthLayout() { + const session = useAuth((state) => state.session); + + if (session) { + const href = (session.role === 'org' ? '/(org)' : '/(tabs)') as any; + return ; + } + + return ; +} \ No newline at end of file diff --git a/src/app/(auth)/interets.tsx b/src/app/(auth)/interets.tsx new file mode 100644 index 0000000..91e253c --- /dev/null +++ b/src/app/(auth)/interets.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function InteretsScreen() { + return ( + + Interets + Base ecran interets + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(auth)/login.tsx b/src/app/(auth)/login.tsx new file mode 100644 index 0000000..71a653b --- /dev/null +++ b/src/app/(auth)/login.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function LoginScreen() { + return ( + + Login + Base ecran login + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(auth)/onboarding.tsx b/src/app/(auth)/onboarding.tsx new file mode 100644 index 0000000..1b56b82 --- /dev/null +++ b/src/app/(auth)/onboarding.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function OnboardingScreen() { + return ( + + Onboarding + Base ecran onboarding + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(org)/_layout.tsx b/src/app/(org)/_layout.tsx new file mode 100644 index 0000000..da5f029 --- /dev/null +++ b/src/app/(org)/_layout.tsx @@ -0,0 +1,12 @@ +import { useAuth } from '@/stores/authStore'; +import { Redirect, Stack } from 'expo-router'; + +export default function OrgLayout() { + const session = useAuth((state) => state.session); + + if (!session) return ; + + if (session.role !== 'org') return ; + + return ; +} \ No newline at end of file diff --git a/src/app/(org)/events.tsx b/src/app/(org)/events.tsx new file mode 100644 index 0000000..681dd65 --- /dev/null +++ b/src/app/(org)/events.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function OrgEventsScreen() { + return ( + + Org Events + Base ecran events organisateur + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(org)/index.tsx b/src/app/(org)/index.tsx new file mode 100644 index 0000000..b5272fd --- /dev/null +++ b/src/app/(org)/index.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function OrgHomeScreen() { + return ( + + Org Home + Base ecran organisation + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(org)/profile.tsx b/src/app/(org)/profile.tsx new file mode 100644 index 0000000..f6313fa --- /dev/null +++ b/src/app/(org)/profile.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function OrgProfileScreen() { + return ( + + Org Profile + Base ecran profil organisateur + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(org)/stats.tsx b/src/app/(org)/stats.tsx new file mode 100644 index 0000000..e4eda4a --- /dev/null +++ b/src/app/(org)/stats.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function OrgStatsScreen() { + return ( + + Org Stats + Base ecran statistiques organisateur + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..d9ec0e3 --- /dev/null +++ b/src/app/(tabs)/_layout.tsx @@ -0,0 +1,12 @@ +import { useAuth } from '@/stores/authStore'; +import { Redirect, Stack } from 'expo-router'; + +export default function TabsLayout() { + const session = useAuth((state) => state.session); + + if (!session) return ; + + if (session.role === 'org') return ; + + return ; +} \ No newline at end of file diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx new file mode 100644 index 0000000..0ea7e14 --- /dev/null +++ b/src/app/(tabs)/index.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function HomeTabScreen() { + return ( + + Accueil + Base ecran accueil tab + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(tabs)/map.tsx b/src/app/(tabs)/map.tsx new file mode 100644 index 0000000..7a06fa6 --- /dev/null +++ b/src/app/(tabs)/map.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function MapTabScreen() { + return ( + + Map + Base ecran carte + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(tabs)/my-events.tsx b/src/app/(tabs)/my-events.tsx new file mode 100644 index 0000000..0574acc --- /dev/null +++ b/src/app/(tabs)/my-events.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function MyEventsTabScreen() { + return ( + + My Events + Base ecran mes events + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/(tabs)/profile.tsx b/src/app/(tabs)/profile.tsx new file mode 100644 index 0000000..1929747 --- /dev/null +++ b/src/app/(tabs)/profile.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function ProfileTabScreen() { + return ( + + Profile + Base ecran profil tab + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 4ee59c7..ade1a9d 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -1,9 +1,17 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { Stack } from 'expo-router'; +import * as SplashScreen from 'expo-splash-screen'; +import { StatusBar } from 'expo-status-bar'; +import { useEffect } from 'react'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; import { queryClient } from '../hooks/queryClient'; import { useAppFonts } from '../theme/typography'; +import { useAuth } from '@/stores/authStore'; + +SplashScreen.preventAutoHideAsync(); + export default function RootLayout() { const [fontsLoaded] = useAppFonts(); @@ -11,9 +19,33 @@ export default function RootLayout() { return null; } + const hydrated = useAuth((state) => state.hydrated); + const hydrate = useAuth((state) => state.hydrate); + + useEffect(() => { hydrate(); }, []); + + const ready = hydrated && fontsLoaded; + + useEffect(() => { + if (ready) SplashScreen.hideAsync(); + }, [ready]); + + if (!ready) return null; + return ( - + + + + + + + + + + + + ); } diff --git a/src/app/create/event.tsx b/src/app/create/event.tsx new file mode 100644 index 0000000..ede95c8 --- /dev/null +++ b/src/app/create/event.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function CreateEventScreen() { + return ( + + Create Event + Base ecran creation event + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/create/post.tsx b/src/app/create/post.tsx new file mode 100644 index 0000000..1838c8b --- /dev/null +++ b/src/app/create/post.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function CreatePostScreen() { + return ( + + Create Post + Base ecran creation post + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, +}); diff --git a/src/app/event/[id].tsx b/src/app/event/[id].tsx new file mode 100644 index 0000000..1c9c1b8 --- /dev/null +++ b/src/app/event/[id].tsx @@ -0,0 +1,41 @@ +import { useLocalSearchParams } from 'expo-router'; +import { StyleSheet, Text, View } from 'react-native'; + +import { colors } from '@/theme/colors'; +import { spacing } from '@/theme/spacing'; +import { typography } from '@/theme/typography'; + +export default function EventDetailScreen() { + const { id } = useLocalSearchParams<{ id?: string | string[] }>(); + const eventId = Array.isArray(id) ? id[0] : id; + + return ( + + Event Detail + Base ecran detail event + ID: {eventId ?? 'non defini'} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.deepBackground, + padding: spacing[24], + justifyContent: 'center', + gap: spacing[8], + }, + title: { + ...typography.screenTitle, + color: colors.textPrimary, + }, + description: { + ...typography.body, + color: colors.textSecondary, + }, + meta: { + ...typography.metadata, + color: colors.textStrong, + }, +}); diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts new file mode 100644 index 0000000..0e0a1c0 --- /dev/null +++ b/src/stores/authStore.ts @@ -0,0 +1,39 @@ +import * as SecureStore from 'expo-secure-store'; +import { create } from 'zustand'; + +export type Role = 'user' | 'org'; +export type Session = { + userId: string; + token: string; + role: Role; +} + +type AuthState = { + session: Session | null; + hydrated: boolean; // Le disque a été lu, la réponse est fiable + hydrate: () => Promise; + signIn: (session: Session) => Promise; + signOut: () => Promise; +}; + +const KEY = 'bahla.session'; + +export const useAuth = create((set) => ({ + session: null, + hydrated: false, + + hydrate: async () => { + const session = await SecureStore.getItemAsync(KEY); + set({ session: session ? JSON.parse(session) : null, hydrated: true }); + }, + + signIn: async (s: Session) => { + await SecureStore.setItemAsync(KEY, JSON.stringify(s)); + set({ session: s }); + }, + + signOut: async () => { + await SecureStore.deleteItemAsync(KEY); + set({ session: null }); + }, +})); \ No newline at end of file