Rework routing

This commit is contained in:
Arthur Belleville 2025-07-20 12:23:18 +02:00
parent 73cbe6356f
commit 8992f58512
No known key found for this signature in database
21 changed files with 191 additions and 68 deletions

View file

@ -11,7 +11,7 @@ import {
Linking,
} from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { useUser } from "@/providers/UserProvider";
import {
User,
@ -31,7 +31,7 @@ import {
import { router } from "expo-router";
export default function SettingsScreen() {
const signOut = useAuth((state) => state.signOut);
const signOut = useAuthStore((state) => state.signOut);
const user = useUser();
// Settings state

View file

@ -5,7 +5,7 @@ import {
Text,
TouchableOpacity,
} from "react-native";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { Avatar, Input } from "@rn-vui/themed";
import { Card } from "@rn-vui/themed";
import { useState } from "react";
@ -22,7 +22,7 @@ import {
} from "lucide-react-native";
export default function ProfileScreen() {
const signOut = useAuth((state) => state.signOut);
const signOut = useAuthStore((state) => state.signOut);
const user = useUser();
const [displayName, setDisplayName] = useState(user.name || "");

View file

@ -1,26 +0,0 @@
import { Redirect, Slot } from "expo-router";
import { useAuth } from "@/stores/auth";
import { ActivityIndicator } from "react-native";
import { useGetUser } from "@/hooks/user";
import { useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
export default function AuthLayout() {
const { loading, initialize } = useAuth();
const queryClient = useQueryClient();
const { user, isLoading: isUserLoading } = useGetUser();
const isLoading = loading || isUserLoading;
useEffect(() => {
initialize(queryClient);
}, []);
if (isLoading) {
return <ActivityIndicator />;
}
if (user) {
return <Redirect href="/(home)/(tabs)" />;
}
return <Slot />;
}

View file

@ -3,17 +3,20 @@ import {
DefaultTheme,
ThemeProvider,
} from "@react-navigation/native";
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { StatusBar } from "expo-status-bar";
import { useEffect } from "react";
import "react-native-reanimated";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { useColorScheme } from "@/hooks/useColorScheme";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { cloneDeep } from "lodash";
import { ActivityIndicator } from "react-native";
import { ActivityIndicator, View, StyleSheet, Image } from "react-native";
import { SplashScreenController } from "@/components/Splash";
import { useInitializeApp } from "@/hooks/auth";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import { LoadingView } from "@/components/LoadingView";
window.structuredClone = cloneDeep;
@ -33,19 +36,6 @@ const queryClient = new QueryClient({
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return <ActivityIndicator />;
}
return (
<GestureHandlerRootView style={{ flex: 1 }}>
@ -53,14 +43,36 @@ export default function RootLayout() {
<ThemeProvider
value={colorScheme === "dark" ? DarkTheme : DefaultTheme}
>
<Stack>
<Stack.Screen name="(home)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
<SplashScreenController />
<RootNavigator />
<StatusBar style="auto" />
</ThemeProvider>
</QueryClientProvider>
</GestureHandlerRootView>
);
}
const RootNavigator = () => {
const { isLoading, isLoggedIn } = useInitializeApp();
if (isLoading) {
return <LoadingView />;
}
console.log("isLoggedIn", isLoggedIn);
return (
<Stack>
<Stack.Protected guard={isLoggedIn}>
<Stack.Screen name="(app)" options={{ headerShown: false }} />
</Stack.Protected>
<Stack.Protected guard={!isLoggedIn}>
<Stack.Screen name="login" options={{ headerShown: false }} />
<Stack.Screen name="signup" options={{ headerShown: false }} />
</Stack.Protected>
<Stack.Screen name="+not-found" />
</Stack>
);
};

View file

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { StyleSheet, View, Text, Image } from "react-native";
import { Button, Input } from "@rn-vui/themed";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { Link } from "expo-router";
import { Mail, Lock } from "lucide-react-native";
import { GoogleLoginButton } from "@/components/GoogleLoginButton";
@ -11,9 +11,9 @@ export default function Auth() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const login = useAuth((state) => state.login);
const authLoading = useAuth((state) => state.loading);
const performOAuth = useAuth((state) => state.performOAuth);
const login = useAuthStore((state) => state.login);
const authLoading = useAuthStore((state) => state.loading);
const performOAuth = useAuthStore((state) => state.performOAuth);
return (
<View style={styles.container}>

View file

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { StyleSheet, View, Text, Image } from "react-native";
import { Button, Input } from "@rn-vui/themed";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { Link } from "expo-router";
import { Mail, Lock, User, Building2 } from "lucide-react-native";
@ -12,8 +12,8 @@ export default function SignUp() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const signUp = useAuth((state) => state.signUp);
const authLoading = useAuth((state) => state.loading);
const signUp = useAuthStore((state) => state.signUp);
const authLoading = useAuthStore((state) => state.loading);
return (
<View style={styles.container}>

View file

@ -1,6 +1,6 @@
import React from "react";
import { StyleSheet, View, Text, TouchableOpacity } from "react-native";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { Svg, Path } from "react-native-svg";
const AppleIcon = ({ color = "#fff", size = 20 }) => (
@ -17,7 +17,7 @@ const AppleIcon = ({ color = "#fff", size = 20 }) => (
);
export const AppleLoginButton = ({ onPress }: { onPress: () => void }) => {
const authLoading = useAuth((state) => state.loading);
const authLoading = useAuthStore((state) => state.loading);
return (
<TouchableOpacity

View file

@ -1,9 +1,9 @@
import React from "react";
import { StyleSheet, View, Text, TouchableOpacity, Image } from "react-native";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
export const GoogleLoginButton = ({ onPress }: { onPress: () => void }) => {
const authLoading = useAuth((state) => state.loading);
const authLoading = useAuthStore((state) => state.loading);
return (
<TouchableOpacity

View file

@ -0,0 +1,98 @@
import { StyleSheet, View } from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withRepeat,
withTiming,
useDerivedValue,
Easing,
} from "react-native-reanimated";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
export const LoadingView = () => {
const rotation = useSharedValue(0);
const width = useSharedValue(80);
rotation.value = withRepeat(
withTiming(360, { easing: Easing.linear, duration: 2000 }),
-1,
false
);
width.value = withRepeat(
withTiming(100, { easing: Easing.linear, duration: 2000 }),
-1,
true
);
const rotationDeg = useDerivedValue(() => {
return `${rotation.value}deg`;
});
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ rotate: rotationDeg.value }],
width: width.value,
};
});
return (
<ThemedView style={styles.loadingContainer}>
<View style={styles.loadingContent}>
<View style={styles.logoContainer}>
<Animated.Image
source={require("@/assets/images/logo.png")}
style={[styles.logo, animatedStyle]}
/>
</View>
<ThemedText type="title" style={styles.title}>
XTablo
</ThemedText>
<ThemedText type="subtitle" style={styles.subtitle}>
Initialisation de l'application...
</ThemedText>
</View>
</ThemedView>
);
};
const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f8fafc",
},
loadingContent: {
alignItems: "center",
paddingHorizontal: 40,
},
logoContainer: {
alignItems: "center",
width: 130,
height: 130,
},
logo: {
width: 80,
height: 80,
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
title: {
fontSize: 28,
fontWeight: "bold",
textAlign: "center",
marginBottom: 8,
color: "#1f2937",
},
subtitle: {
fontSize: 16,
textAlign: "center",
marginBottom: 32,
color: "#6b7280",
opacity: 0.8,
},
});

View file

@ -0,0 +1,17 @@
import { SplashScreen } from "expo-router";
import { useInitializeApp } from "@/hooks/auth";
import { useFonts } from "expo-font";
export function SplashScreenController() {
const { isLoading } = useInitializeApp();
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
if (!isLoading && loaded) {
SplashScreen.hideAsync();
}
return null;
}

18
xtablo-expo/hooks/auth.ts Normal file
View file

@ -0,0 +1,18 @@
import { useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { useAuthStore } from "@/stores/auth";
import { useGetUser } from "@/hooks/user";
export const useInitializeApp = () => {
const { loading, initialize } = useAuthStore();
const queryClient = useQueryClient();
const { user, isLoading: isUserLoading } = useGetUser();
const isLoading = loading || isUserLoading;
useEffect(() => {
initialize(queryClient);
}, []);
return { isLoading, isLoggedIn: !!user };
};

View file

@ -3,7 +3,7 @@ import { useUser } from "@/providers/UserProvider";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { TabloInsert, UserTablo } from "@/types/tablos.types";
import { api } from "@/lib/api";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { Alert } from "react-native";
// type TabloInsert = Tablo["Insert"];
@ -28,7 +28,7 @@ export const useTablosList = () => {
};
export const useCreateTablo = () => {
const session = useAuth((state) => state.session);
const session = useAuthStore((state) => state.session);
const queryClient = useQueryClient();
return useMutation({
@ -53,7 +53,7 @@ export const useCreateTablo = () => {
// Delete tablo (soft delete)
export const useDeleteTablo = () => {
const session = useAuth((state) => state.session);
const session = useAuthStore((state) => state.session);
const queryClient = useQueryClient();
return useMutation({

View file

@ -1,6 +1,6 @@
import { api } from "@/lib/api";
import { Tables } from "@/types/database.types";
import { useAuth } from "@/stores/auth";
import { useAuthStore } from "@/stores/auth";
import { useQuery } from "@tanstack/react-query";
type User = Tables<"profiles"> & {
@ -8,7 +8,7 @@ type User = Tables<"profiles"> & {
};
export const useGetUser = (): { user: User | null; isLoading: boolean } => {
const session = useAuth((state) => state.session);
const session = useAuthStore((state) => state.session);
const { data, isLoading } = useQuery<User | null>({
queryKey: ["user"],
queryFn: async () => {

View file

@ -10,6 +10,7 @@ import { QueryClient } from "@tanstack/react-query";
interface AuthState {
session: Session | null;
loading: boolean;
initialized: boolean;
initialize: (queryClient: QueryClient) => Promise<void>;
setSession: (session: Session | null) => void;
login: (email: string, password: string) => Promise<void>;
@ -28,8 +29,9 @@ interface AuthState {
WebBrowser.maybeCompleteAuthSession();
const redirectTo = makeRedirectUri({ path: "/(home)/(tabs)" });
export const useAuth = create<AuthState>((set, get) => ({
export const useAuthStore = create<AuthState>((set, get) => ({
loading: true,
initialized: false,
session: null,
setSession: (session: Session | null) => set({ session }),
initialize: async (queryClient: QueryClient) => {
@ -61,6 +63,8 @@ export const useAuth = create<AuthState>((set, get) => ({
console.error("Auth initialization error:", error);
} finally {
set({ loading: false });
set({ initialized: true });
queryClient.invalidateQueries({ queryKey: ["user"] });
}
},
login: async (email: string, password: string) => {