Stockage persistent des requêtes
This commit is contained in:
parent
1c52ff5a10
commit
db7a0b970d
@ -3,7 +3,10 @@ import { Stack, useNavigationContainerRef } from 'expo-router'
|
|||||||
import { StatusBar } from 'expo-status-bar'
|
import { StatusBar } from 'expo-status-bar'
|
||||||
import { Provider as StoreProvider } from 'react-redux'
|
import { Provider as StoreProvider } from 'react-redux'
|
||||||
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper'
|
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 { useReactNavigationDevTools } from '@dev-plugins/react-navigation'
|
||||||
import { useReactQueryDevTools } from '@dev-plugins/react-query'
|
import { useReactQueryDevTools } from '@dev-plugins/react-query'
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme'
|
import { useColorScheme } from '@/hooks/useColorScheme'
|
||||||
@ -12,7 +15,15 @@ import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
|
|||||||
import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
|
import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
|
||||||
import LoginProvider from '@/components/LoginProvider'
|
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() {
|
export default function RootLayout() {
|
||||||
useStartGeolocationServiceEffect()
|
useStartGeolocationServiceEffect()
|
||||||
@ -21,12 +32,18 @@ export default function RootLayout() {
|
|||||||
|
|
||||||
const navigationRef = useNavigationContainerRef()
|
const navigationRef = useNavigationContainerRef()
|
||||||
useReactNavigationDevTools(navigationRef)
|
useReactNavigationDevTools(navigationRef)
|
||||||
|
|
||||||
useReactQueryDevTools(queryClient)
|
useReactQueryDevTools(queryClient)
|
||||||
|
|
||||||
|
const asyncStoragePersister = createAsyncStoragePersister({
|
||||||
|
storage: AsyncStorage,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StoreProvider store={store}>
|
<StoreProvider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<PersistQueryClientProvider
|
||||||
|
client={queryClient}
|
||||||
|
persistOptions={{ persister: asyncStoragePersister }}
|
||||||
|
onSuccess={() => queryClient.resumePausedMutations().then(() => queryClient.invalidateQueries())}>
|
||||||
<LoginProvider loginRedirect={'/login'}>
|
<LoginProvider loginRedirect={'/login'}>
|
||||||
<PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}>
|
<PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}>
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
@ -39,7 +56,7 @@ export default function RootLayout() {
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
</LoginProvider>
|
</LoginProvider>
|
||||||
</QueryClientProvider>
|
</PersistQueryClientProvider>
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,23 @@ export default function LoginProvider({ loginRedirect, children }: Props) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Renouvellement auto du jeton d'authentification
|
|
||||||
useEffect(() => {
|
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 { name, token } = auth
|
||||||
const password = SecureStore.getItem('apiPassword')
|
const password = SecureStore.getItem('apiPassword')
|
||||||
if (name === null || (password === null && token === null))
|
if (name === null || (password === null && token === null))
|
||||||
@ -45,13 +60,7 @@ export default function LoginProvider({ loginRedirect, children }: Props) {
|
|||||||
loginMutation.mutate({ name, password })
|
loginMutation.mutate({ name, password })
|
||||||
}, waitTime)
|
}, waitTime)
|
||||||
return () => clearTimeout(timeout)
|
return () => clearTimeout(timeout)
|
||||||
}, [auth])
|
}, [auth, authLogin])
|
||||||
|
|
||||||
// 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])
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{children}
|
{children}
|
||||||
|
@ -24,14 +24,15 @@ type LoginProps = {
|
|||||||
|
|
||||||
export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginProps) => {
|
export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginProps) => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ name, password }: LoginForm) => {
|
mutationFn: async ({ name, password }: LoginForm) => {
|
||||||
return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/auth/login/`, {
|
return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/auth/login/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ name: name, password: password })
|
body: JSON.stringify({ name: name, password: password })
|
||||||
}).then(resp => resp.json())
|
}).then(resp => resp.json())
|
||||||
},
|
},
|
||||||
onSuccess: (data, { name, password }: LoginForm) => {
|
networkMode: 'always',
|
||||||
|
onSuccess: async (data, { name, password }: LoginForm) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
if (onError)
|
if (onError)
|
||||||
onError({ response: data })
|
onError({ response: data })
|
||||||
@ -41,7 +42,7 @@ export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginPro
|
|||||||
if (onPostSuccess)
|
if (onPostSuccess)
|
||||||
onPostSuccess()
|
onPostSuccess()
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: async (error: Error) => {
|
||||||
if (onError)
|
if (onError)
|
||||||
onError({ error: error })
|
onError({ error: error })
|
||||||
}
|
}
|
||||||
|
79
client/package-lock.json
generated
79
client/package-lock.json
generated
@ -13,10 +13,13 @@
|
|||||||
"@expo/vector-icons": "^14.0.2",
|
"@expo/vector-icons": "^14.0.2",
|
||||||
"@maplibre/maplibre-react-native": "^10.0.0-alpha.28",
|
"@maplibre/maplibre-react-native": "^10.0.0-alpha.28",
|
||||||
"@pchmn/expo-material3-theme": "github:pchmn/expo-material3-theme",
|
"@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/bottom-tabs": "^7.0.0",
|
||||||
"@react-navigation/native": "^7.0.0",
|
"@react-navigation/native": "^7.0.0",
|
||||||
"@reduxjs/toolkit": "^2.4.0",
|
"@reduxjs/toolkit": "^2.4.0",
|
||||||
|
"@tanstack/query-async-storage-persister": "^5.62.7",
|
||||||
"@tanstack/react-query": "^5.62.7",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
|
"@tanstack/react-query-persist-client": "^5.62.7",
|
||||||
"@turf/circle": "^7.1.0",
|
"@turf/circle": "^7.1.0",
|
||||||
"expo": "~52.0.11",
|
"expo": "~52.0.11",
|
||||||
"expo-background-fetch": "~13.0.3",
|
"expo-background-fetch": "~13.0.3",
|
||||||
@ -3846,6 +3849,18 @@
|
|||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
"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": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.76.3",
|
"version": "0.76.3",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.3.tgz",
|
||||||
@ -4507,6 +4522,19 @@
|
|||||||
"@sinonjs/commons": "^3.0.0"
|
"@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": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.62.7",
|
"version": "5.62.7",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz",
|
||||||
@ -4517,6 +4545,19 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"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": {
|
"node_modules/@tanstack/react-query": {
|
||||||
"version": "5.62.7",
|
"version": "5.62.7",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz",
|
||||||
@ -4533,6 +4574,23 @@
|
|||||||
"react": "^18 || ^19"
|
"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": {
|
"node_modules/@tootallnate/once": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||||
@ -9472,6 +9530,15 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/is-plain-object": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
"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==",
|
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
|
@ -22,7 +22,9 @@
|
|||||||
"@react-navigation/bottom-tabs": "^7.0.0",
|
"@react-navigation/bottom-tabs": "^7.0.0",
|
||||||
"@react-navigation/native": "^7.0.0",
|
"@react-navigation/native": "^7.0.0",
|
||||||
"@reduxjs/toolkit": "^2.4.0",
|
"@reduxjs/toolkit": "^2.4.0",
|
||||||
|
"@tanstack/query-async-storage-persister": "^5.62.7",
|
||||||
"@tanstack/react-query": "^5.62.7",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
|
"@tanstack/react-query-persist-client": "^5.62.7",
|
||||||
"@turf/circle": "^7.1.0",
|
"@turf/circle": "^7.1.0",
|
||||||
"expo": "~52.0.11",
|
"expo": "~52.0.11",
|
||||||
"expo-background-fetch": "~13.0.3",
|
"expo-background-fetch": "~13.0.3",
|
||||||
@ -56,7 +58,8 @@
|
|||||||
"react-native-screens": "~4.1.0",
|
"react-native-screens": "~4.1.0",
|
||||||
"react-native-web": "~0.19.13",
|
"react-native-web": "~0.19.13",
|
||||||
"react-native-webview": "13.12.2",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
@ -17,3 +17,27 @@ export function setItem(key: string, value: string): void {
|
|||||||
export async function setItemAsync(key: string, value: string): Promise<void> {
|
export async function setItemAsync(key: string, value: string): Promise<void> {
|
||||||
localStorage.setItem(key, value)
|
localStorage.setItem(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||||
|
|
||||||
|
export async function deleteItemAsync(key: string): Promise<void> {
|
||||||
|
return AsyncStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItem(key: string): string | null {
|
||||||
|
return Promise.apply(AsyncStorage.getItem(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getItemAsync(key: string): Promise<string | null> {
|
||||||
|
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<void> {
|
||||||
|
return AsyncStorage.setItem(key, value)
|
||||||
|
}
|
||||||
|
*/
|
@ -15,9 +15,9 @@ export interface AuthPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: AuthState = {
|
const initialState: AuthState = {
|
||||||
loggedIn: SecureStore.getItem('apiName') !== null && SecureStore.getItem('apiToken') !== null,
|
loggedIn: false,
|
||||||
name: SecureStore.getItem('apiName'),
|
name: null,
|
||||||
token: SecureStore.getItem('apiToken'),
|
token: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authSlice = createSlice({
|
export const authSlice = createSlice({
|
||||||
|
Loading…
Reference in New Issue
Block a user