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