diff --git a/client/app/_layout.tsx b/client/app/_layout.tsx
index 9ca2ec0..e79865c 100644
--- a/client/app/_layout.tsx
+++ b/client/app/_layout.tsx
@@ -3,7 +3,10 @@ import { Stack, useNavigationContainerRef } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import { Provider as StoreProvider } from 'react-redux'
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import AsyncStorage from '@react-native-async-storage/async-storage'
+import { QueryClient } from '@tanstack/react-query'
+import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
+import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
import { useReactNavigationDevTools } from '@dev-plugins/react-navigation'
import { useReactQueryDevTools } from '@dev-plugins/react-query'
import { useColorScheme } from '@/hooks/useColorScheme'
@@ -12,7 +15,15 @@ import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
import LoginProvider from '@/components/LoginProvider'
-const queryClient = new QueryClient()
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ gcTime: 24 * 60 * 60 * 1000, // 24h
+ staleTime: 2000,
+ retry: 5,
+ }
+ }
+})
export default function RootLayout() {
useStartGeolocationServiceEffect()
@@ -21,12 +32,18 @@ export default function RootLayout() {
const navigationRef = useNavigationContainerRef()
useReactNavigationDevTools(navigationRef)
-
useReactQueryDevTools(queryClient)
+ const asyncStoragePersister = createAsyncStoragePersister({
+ storage: AsyncStorage,
+ })
+
return (
-
+ queryClient.resumePausedMutations().then(() => queryClient.invalidateQueries())}>
@@ -39,7 +56,7 @@ export default function RootLayout() {
-
+
)
}
diff --git a/client/components/LoginProvider.tsx b/client/components/LoginProvider.tsx
index 4b48f12..0ceab3b 100644
--- a/client/components/LoginProvider.tsx
+++ b/client/components/LoginProvider.tsx
@@ -25,8 +25,23 @@ export default function LoginProvider({ loginRedirect, children }: Props) {
}
})
- // Renouvellement auto du jeton d'authentification
useEffect(() => {
+ (async () => {
+ const storedName = await SecureStore.getItemAsync('apiName')
+ const storedToken = await SecureStore.getItemAsync('apiToken')
+ if (!auth.loggedIn && storedName !== null && storedName !== auth.name && storedToken !== auth.token) {
+ authLogin({ name: storedName, token: storedToken })
+ return
+ }
+
+ // Si on est pas connecté⋅e, on reste sur la fenêtre de connexion
+ if (!auth.loggedIn && route.pathname !== loginRedirect)
+ router.navigate(loginRedirect)
+ })()
+ }, [auth, authLogin, router, route])
+
+ useEffect(() => {
+ // Renouvellement auto du jeton d'authentification
const { name, token } = auth
const password = SecureStore.getItem('apiPassword')
if (name === null || (password === null && token === null))
@@ -45,13 +60,7 @@ export default function LoginProvider({ loginRedirect, children }: Props) {
loginMutation.mutate({ name, password })
}, waitTime)
return () => clearTimeout(timeout)
- }, [auth])
-
- // Si on est pas connecté⋅e, on reste sur la fenêtre de connexion
- useEffect(() => {
- if (!auth.loggedIn && route.pathname !== loginRedirect)
- router.navigate(loginRedirect)
- }, [auth, route, router])
+ }, [auth, authLogin])
return <>
{children}
diff --git a/client/hooks/mutations/useLoginMutation.ts b/client/hooks/mutations/useLoginMutation.ts
index 3c04bbd..ab22664 100644
--- a/client/hooks/mutations/useLoginMutation.ts
+++ b/client/hooks/mutations/useLoginMutation.ts
@@ -24,14 +24,15 @@ type LoginProps = {
export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginProps) => {
return useMutation({
- mutationFn: ({ name, password }: LoginForm) => {
+ mutationFn: async ({ name, password }: LoginForm) => {
return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/auth/login/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: name, password: password })
}).then(resp => resp.json())
},
- onSuccess: (data, { name, password }: LoginForm) => {
+ networkMode: 'always',
+ onSuccess: async (data, { name, password }: LoginForm) => {
if (data.error) {
if (onError)
onError({ response: data })
@@ -41,7 +42,7 @@ export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginPro
if (onPostSuccess)
onPostSuccess()
},
- onError: (error: Error) => {
+ onError: async (error: Error) => {
if (onError)
onError({ error: error })
}
diff --git a/client/package-lock.json b/client/package-lock.json
index b1fee79..17a8047 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -13,10 +13,13 @@
"@expo/vector-icons": "^14.0.2",
"@maplibre/maplibre-react-native": "^10.0.0-alpha.28",
"@pchmn/expo-material3-theme": "github:pchmn/expo-material3-theme",
+ "@react-native-async-storage/async-storage": "1.23.1",
"@react-navigation/bottom-tabs": "^7.0.0",
"@react-navigation/native": "^7.0.0",
"@reduxjs/toolkit": "^2.4.0",
+ "@tanstack/query-async-storage-persister": "^5.62.7",
"@tanstack/react-query": "^5.62.7",
+ "@tanstack/react-query-persist-client": "^5.62.7",
"@turf/circle": "^7.1.0",
"expo": "~52.0.11",
"expo-background-fetch": "~13.0.3",
@@ -3846,6 +3849,18 @@
"react": "^16.8 || ^17.0 || ^18.0"
}
},
+ "node_modules/@react-native-async-storage/async-storage": {
+ "version": "1.23.1",
+ "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz",
+ "integrity": "sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==",
+ "license": "MIT",
+ "dependencies": {
+ "merge-options": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react-native": "^0.0.0-0 || >=0.60 <1.0"
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.76.3",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.3.tgz",
@@ -4507,6 +4522,19 @@
"@sinonjs/commons": "^3.0.0"
}
},
+ "node_modules/@tanstack/query-async-storage-persister": {
+ "version": "5.62.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.62.7.tgz",
+ "integrity": "sha512-8qSJ1oTnGhCikPADWd35xrswyYtmkqYnakWgqeXjxL+F+qPGgsfexNUlBu9TNqo9eAP/1ia4Lt5Ks2fTsMzBgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-persist-client-core": "5.62.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@tanstack/query-core": {
"version": "5.62.7",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz",
@@ -4517,6 +4545,19 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@tanstack/query-persist-client-core": {
+ "version": "5.62.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.62.7.tgz",
+ "integrity": "sha512-9HcaD9rEp2nGWnrw2osK5UCSKJbJKEdn+MEhVVfnUPSFN7MZFpFZxpRCHJi3fRpWOYsVeH9EFODX+aoJaniJMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.62.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@tanstack/react-query": {
"version": "5.62.7",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz",
@@ -4533,6 +4574,23 @@
"react": "^18 || ^19"
}
},
+ "node_modules/@tanstack/react-query-persist-client": {
+ "version": "5.62.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.62.7.tgz",
+ "integrity": "sha512-RmEJ3YvsK7lv1Of3CCXBgHDtoZVwHMtTKCTegZz+xijVJsgJaNNfel4YTpbQ0ydnWT2IcohdqnHUtBE6p1KCIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-persist-client-core": "5.62.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.62.7",
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -9472,6 +9530,15 @@
"node": ">=8"
}
},
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -11406,6 +11473,18 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
+ "node_modules/merge-options": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
+ "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
diff --git a/client/package.json b/client/package.json
index 73e476b..df46b4e 100644
--- a/client/package.json
+++ b/client/package.json
@@ -22,7 +22,9 @@
"@react-navigation/bottom-tabs": "^7.0.0",
"@react-navigation/native": "^7.0.0",
"@reduxjs/toolkit": "^2.4.0",
+ "@tanstack/query-async-storage-persister": "^5.62.7",
"@tanstack/react-query": "^5.62.7",
+ "@tanstack/react-query-persist-client": "^5.62.7",
"@turf/circle": "^7.1.0",
"expo": "~52.0.11",
"expo-background-fetch": "~13.0.3",
@@ -56,7 +58,8 @@
"react-native-screens": "~4.1.0",
"react-native-web": "~0.19.13",
"react-native-webview": "13.12.2",
- "react-redux": "^9.1.2"
+ "react-redux": "^9.1.2",
+ "@react-native-async-storage/async-storage": "1.23.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
diff --git a/client/utils/SecureStore.web.ts b/client/utils/SecureStore.web.ts
index ed0822b..68a23a1 100644
--- a/client/utils/SecureStore.web.ts
+++ b/client/utils/SecureStore.web.ts
@@ -17,3 +17,27 @@ export function setItem(key: string, value: string): void {
export async function setItemAsync(key: string, value: string): Promise {
localStorage.setItem(key, value)
}
+
+/**
+import AsyncStorage from '@react-native-async-storage/async-storage'
+
+export async function deleteItemAsync(key: string): Promise {
+ return AsyncStorage.removeItem(key)
+}
+
+export function getItem(key: string): string | null {
+ return Promise.apply(AsyncStorage.getItem(key))
+}
+
+export async function getItemAsync(key: string): Promise {
+ return AsyncStorage.getItem(key)
+}
+
+export function setItem(key: string, value: string): void {
+ AsyncStorage.setItem(key, value)
+}
+
+export async function setItemAsync(key: string, value: string): Promise {
+ return AsyncStorage.setItem(key, value)
+}
+ */
\ No newline at end of file
diff --git a/client/utils/features/location/authSlice.ts b/client/utils/features/location/authSlice.ts
index e8ce9c7..88c9c5e 100644
--- a/client/utils/features/location/authSlice.ts
+++ b/client/utils/features/location/authSlice.ts
@@ -15,9 +15,9 @@ export interface AuthPayload {
}
const initialState: AuthState = {
- loggedIn: SecureStore.getItem('apiName') !== null && SecureStore.getItem('apiToken') !== null,
- name: SecureStore.getItem('apiName'),
- token: SecureStore.getItem('apiToken'),
+ loggedIn: false,
+ name: null,
+ token: null,
}
export const authSlice = createSlice({