Major refactor of auth, refresh, and api calls.
This commit is contained in:
parent
c099275b31
commit
c54d3832a3
@ -1,93 +0,0 @@
|
|||||||
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
||||||
import { useRouter } from '@tanstack/react-router'
|
|
||||||
import React, { useCallback, useMemo, useRef } from 'react'
|
|
||||||
import { ApiError } from './api/ApiError'
|
|
||||||
import ExpiredTokenError from './api/ExpiredTokenError'
|
|
||||||
import refresh, { RefreshTokenError } from './api/refresh'
|
|
||||||
import { useAuth } from './auth'
|
|
||||||
|
|
||||||
const AuthAwareQueryClientProvider = ({ children }: React.PropsWithChildren) => {
|
|
||||||
const { putToken } = useAuth()
|
|
||||||
const router = useRouter()
|
|
||||||
const refreshing = useRef(false)
|
|
||||||
|
|
||||||
const doRefresh = useCallback(async () => {
|
|
||||||
putToken(null)
|
|
||||||
if (!refreshing.current) {
|
|
||||||
refreshing.current = true
|
|
||||||
try {
|
|
||||||
const { accessToken: token, expires, username } = await refresh()
|
|
||||||
putToken({
|
|
||||||
token,
|
|
||||||
expires,
|
|
||||||
username
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof RefreshTokenError) {
|
|
||||||
router.navigate({
|
|
||||||
to: '/login',
|
|
||||||
search: {
|
|
||||||
reason: error.reason,
|
|
||||||
redirect: router.state.location.href
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (error instanceof ApiError) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refreshing.current = false
|
|
||||||
}
|
|
||||||
}, [putToken, router])
|
|
||||||
|
|
||||||
const queryClient = useMemo(
|
|
||||||
() =>
|
|
||||||
new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
mutations: {
|
|
||||||
onError(error) {
|
|
||||||
if (error instanceof ExpiredTokenError) {
|
|
||||||
doRefresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
queries: {
|
|
||||||
retry(failureCount, error) {
|
|
||||||
if (
|
|
||||||
error instanceof ExpiredTokenError ||
|
|
||||||
(error instanceof ApiError && error.status === 404)
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return failureCount <= 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
retryDelay(failureCount, error) {
|
|
||||||
if (error instanceof ExpiredTokenError) {
|
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
return failureCount * 1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
queryCache: new QueryCache({
|
|
||||||
onError(error) {
|
|
||||||
if (error instanceof ExpiredTokenError) {
|
|
||||||
doRefresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
[doRefresh]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
{children}
|
|
||||||
<ReactQueryDevtools position="right" buttonPosition="top-right" />
|
|
||||||
</QueryClientProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthAwareQueryClientProvider
|
|
@ -1,4 +1,4 @@
|
|||||||
import React, { createContext, useContext, useReducer } from 'react'
|
import { createContext, PropsWithChildren, useContext, useReducer } from 'react'
|
||||||
import AccessToken from './types/AccessToken'
|
import AccessToken from './types/AccessToken'
|
||||||
|
|
||||||
export interface AuthContextType {
|
export interface AuthContextType {
|
||||||
@ -36,7 +36,7 @@ const authReducer = (_state: AuthReducerState, action: AuthReducerAction): AuthR
|
|||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | null>(null)
|
const AuthContext = createContext<AuthContextType | null>(null)
|
||||||
|
|
||||||
export const AuthProvider = ({ children }: React.PropsWithChildren) => {
|
export const AuthProvider = ({ children }: PropsWithChildren) => {
|
||||||
const [state, dispatch] = useReducer(authReducer, initialState)
|
const [state, dispatch] = useReducer(authReducer, initialState)
|
||||||
|
|
||||||
return (
|
return (
|
73
src/RefreshProvider.tsx
Normal file
73
src/RefreshProvider.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { useRouter } from '@tanstack/react-router'
|
||||||
|
import { createContext, useContext, PropsWithChildren, useRef, useCallback } from 'react'
|
||||||
|
import { ApiError } from './api/ApiError'
|
||||||
|
import refresh, { RefreshTokenError } from './api/refresh'
|
||||||
|
import { useAuth } from './AuthProvider'
|
||||||
|
import Refresh from './types/Refresh'
|
||||||
|
|
||||||
|
export interface RefreshContextType {
|
||||||
|
refresh: Refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
const RefreshContext = createContext<RefreshContextType | null>(null)
|
||||||
|
|
||||||
|
export const useRefresh = () => {
|
||||||
|
const refreshContext = useContext(RefreshContext)
|
||||||
|
if (refreshContext === null) {
|
||||||
|
throw new Error('refreshContext is null')
|
||||||
|
}
|
||||||
|
return refreshContext.refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
const RefreshProvider = ({ children }: PropsWithChildren) => {
|
||||||
|
const { putToken } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
const refreshing = useRef(false)
|
||||||
|
|
||||||
|
const doRefresh: Refresh = useCallback(async () => {
|
||||||
|
putToken(null)
|
||||||
|
if (!refreshing.current) {
|
||||||
|
refreshing.current = true
|
||||||
|
try {
|
||||||
|
const { accessToken: token, expires, username } = await refresh()
|
||||||
|
putToken({
|
||||||
|
token,
|
||||||
|
expires,
|
||||||
|
username
|
||||||
|
})
|
||||||
|
refreshing.current = false
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
expires,
|
||||||
|
username
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof RefreshTokenError) {
|
||||||
|
router.navigate({
|
||||||
|
to: '/login',
|
||||||
|
search: {
|
||||||
|
reason: error.reason,
|
||||||
|
redirect: router.state.location.href
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (error instanceof ApiError) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
refreshing.current = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}, [putToken, router])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RefreshContext.Provider
|
||||||
|
value={{
|
||||||
|
refresh: doRefresh
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RefreshContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RefreshProvider
|
@ -1,4 +1,4 @@
|
|||||||
import { AuthContextType } from './auth'
|
import { AuthContextType } from './AuthProvider'
|
||||||
|
|
||||||
export default interface RouterContext {
|
export default interface RouterContext {
|
||||||
auth: AuthContextType
|
auth: AuthContextType
|
||||||
|
62
src/api/apiCallFactory.ts
Normal file
62
src/api/apiCallFactory.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import AccessToken from '../types/AccessToken'
|
||||||
|
import Refresh from '../types/Refresh'
|
||||||
|
import { ApiError } from './ApiError'
|
||||||
|
|
||||||
|
export interface ApiCallDeps {
|
||||||
|
accessToken: AccessToken | null
|
||||||
|
endpoint: string
|
||||||
|
query?: string
|
||||||
|
refresh: Refresh
|
||||||
|
signal: AbortSignal
|
||||||
|
body?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||||
|
|
||||||
|
const getApiCallFactory =
|
||||||
|
(method: Method) =>
|
||||||
|
<T>(handleBody?: (raw: any) => T) =>
|
||||||
|
async ({ accessToken, endpoint, refresh, signal, body }: ApiCallDeps): Promise<T> => {
|
||||||
|
const headers = new Headers()
|
||||||
|
if (accessToken) {
|
||||||
|
headers.set('Authorization', `Bearer ${accessToken.token}`)
|
||||||
|
}
|
||||||
|
if (body) {
|
||||||
|
headers.set('Content-type', 'application/json')
|
||||||
|
}
|
||||||
|
const url = import.meta.env.VITE_MME_API_URL + endpoint
|
||||||
|
const response = await fetch(url, {
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
mode: 'cors',
|
||||||
|
method
|
||||||
|
})
|
||||||
|
if (response.ok && handleBody) {
|
||||||
|
return handleBody(await response.json())
|
||||||
|
} else if (response.status === 401) {
|
||||||
|
const newToken = await refresh()
|
||||||
|
if (newToken === null) {
|
||||||
|
throw new ApiError(401, 'Could not get a refreshed access token.')
|
||||||
|
}
|
||||||
|
headers.set('Authorization', `Bearer ${newToken.token}`)
|
||||||
|
const retry = await fetch(url, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
mode: 'cors',
|
||||||
|
method
|
||||||
|
})
|
||||||
|
if (retry.ok && handleBody) {
|
||||||
|
return handleBody(await retry.json())
|
||||||
|
} else {
|
||||||
|
throw new ApiError(retry.status, retry.statusText)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ApiError(response.status, response.statusText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCallFactory = getApiCallFactory('GET')
|
||||||
|
export const postCallFactory = getApiCallFactory('POST')
|
||||||
|
export const putCallFactory = getApiCallFactory('PUT')
|
||||||
|
export const deleteCallFactory = getApiCallFactory('DELETE')
|
@ -1,72 +1,24 @@
|
|||||||
import AccessToken from '../types/AccessToken'
|
import AccessToken from '../types/AccessToken'
|
||||||
import { ApiError } from './ApiError'
|
import Refresh from '../types/Refresh'
|
||||||
import ExpiredTokenError from './ExpiredTokenError'
|
import { getCallFactory } from './apiCallFactory'
|
||||||
import { toImageView } from './types/ImageView'
|
import { toRecipeInfosView } from './types/RecipeInfosView'
|
||||||
import RecipeInfosView, { RawRecipeInfosView } from './types/RecipeInfosView'
|
|
||||||
import { addBearer } from './util'
|
|
||||||
|
|
||||||
export interface GetRecipeInfosDeps {
|
export interface GetRecipeInfosDeps {
|
||||||
abortSignal: AbortSignal
|
|
||||||
accessToken: AccessToken | null
|
accessToken: AccessToken | null
|
||||||
pageNumber: number
|
pageNumber: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
|
refresh: Refresh
|
||||||
|
signal: AbortSignal
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRecipeInfos = async ({
|
const doGetRecipeInfos = getCallFactory(toRecipeInfosView)
|
||||||
abortSignal,
|
|
||||||
|
const getRecipeInfos = ({ accessToken, pageNumber, pageSize, refresh, signal }: GetRecipeInfosDeps) =>
|
||||||
|
doGetRecipeInfos({
|
||||||
accessToken,
|
accessToken,
|
||||||
pageNumber,
|
endpoint: `/recipes?page=${pageNumber}&size=${pageSize}`,
|
||||||
pageSize
|
refresh,
|
||||||
}: GetRecipeInfosDeps): Promise<RecipeInfosView> => {
|
signal
|
||||||
const headers = new Headers()
|
|
||||||
if (accessToken !== null) {
|
|
||||||
addBearer(headers, accessToken)
|
|
||||||
}
|
|
||||||
const response = await fetch(import.meta.env.VITE_MME_API_URL + `/recipes?page=${pageNumber}&size=${pageSize}`, {
|
|
||||||
signal: abortSignal,
|
|
||||||
headers,
|
|
||||||
mode: 'cors'
|
|
||||||
})
|
})
|
||||||
if (response.ok) {
|
|
||||||
const { pageNumber, pageSize, content } = (await response.json()) as RawRecipeInfosView
|
|
||||||
return {
|
|
||||||
pageNumber,
|
|
||||||
pageSize,
|
|
||||||
content: content.map(
|
|
||||||
({
|
|
||||||
id,
|
|
||||||
updated: rawUpdated,
|
|
||||||
title,
|
|
||||||
preparationTime,
|
|
||||||
cookingTime,
|
|
||||||
totalTime,
|
|
||||||
ownerId,
|
|
||||||
owner,
|
|
||||||
isPublic,
|
|
||||||
starCount,
|
|
||||||
mainImage: rawMainImage,
|
|
||||||
slug
|
|
||||||
}) => ({
|
|
||||||
id,
|
|
||||||
updated: new Date(rawUpdated),
|
|
||||||
title,
|
|
||||||
preparationTime,
|
|
||||||
cookingTime,
|
|
||||||
totalTime,
|
|
||||||
ownerId,
|
|
||||||
owner,
|
|
||||||
isPublic,
|
|
||||||
starCount,
|
|
||||||
mainImage: rawMainImage !== null ? toImageView(rawMainImage) : null,
|
|
||||||
slug
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (response.status === 401) {
|
|
||||||
throw new ExpiredTokenError()
|
|
||||||
} else {
|
|
||||||
throw new ApiError(response.status, response.statusText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getRecipeInfos
|
export default getRecipeInfos
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import ImageView, { RawImageView } from './ImageView'
|
import ImageView, { RawImageView, toImageView } from './ImageView'
|
||||||
import UserInfoView from './UserInfoView'
|
import UserInfoView from './UserInfoView'
|
||||||
|
|
||||||
export interface RawRecipeInfoView {
|
export interface RawRecipeInfoView {
|
||||||
id: number
|
id: number
|
||||||
updated: string
|
created: string
|
||||||
|
modified: string | null
|
||||||
title: string
|
title: string
|
||||||
preparationTime: number
|
preparationTime: number
|
||||||
cookingTime: number
|
cookingTime: number
|
||||||
totalTime: number
|
totalTime: number
|
||||||
ownerId: number
|
|
||||||
owner: UserInfoView
|
owner: UserInfoView
|
||||||
isPublic: boolean
|
isPublic: boolean
|
||||||
starCount: number
|
starCount: number
|
||||||
@ -18,7 +18,8 @@ export interface RawRecipeInfoView {
|
|||||||
|
|
||||||
interface RecipeInfoView {
|
interface RecipeInfoView {
|
||||||
id: number
|
id: number
|
||||||
updated: Date
|
created: Date
|
||||||
|
modified: Date | null
|
||||||
title: string
|
title: string
|
||||||
preparationTime: number
|
preparationTime: number
|
||||||
cookingTime: number
|
cookingTime: number
|
||||||
@ -30,4 +31,32 @@ interface RecipeInfoView {
|
|||||||
slug: string
|
slug: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toRecipeInfoView = ({
|
||||||
|
id,
|
||||||
|
created: rawCreated,
|
||||||
|
modified: rawModified,
|
||||||
|
title,
|
||||||
|
preparationTime,
|
||||||
|
cookingTime,
|
||||||
|
totalTime,
|
||||||
|
owner,
|
||||||
|
isPublic,
|
||||||
|
starCount,
|
||||||
|
mainImage: rawMainImage,
|
||||||
|
slug
|
||||||
|
}: RawRecipeInfoView): RecipeInfoView => ({
|
||||||
|
id,
|
||||||
|
created: new Date(rawCreated),
|
||||||
|
modified: rawModified !== null ? new Date(rawModified) : null,
|
||||||
|
title,
|
||||||
|
preparationTime,
|
||||||
|
cookingTime,
|
||||||
|
totalTime,
|
||||||
|
owner,
|
||||||
|
isPublic,
|
||||||
|
starCount,
|
||||||
|
mainImage: rawMainImage !== null ? toImageView(rawMainImage) : null,
|
||||||
|
slug
|
||||||
|
})
|
||||||
|
|
||||||
export default RecipeInfoView
|
export default RecipeInfoView
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import RecipeInfoView, { RawRecipeInfoView } from './RecipeInfoView'
|
import RecipeInfoView, { RawRecipeInfoView, toRecipeInfoView } from './RecipeInfoView'
|
||||||
|
|
||||||
export interface RawRecipeInfosView {
|
export interface RawRecipeInfosView {
|
||||||
pageNumber: number
|
pageNumber: number
|
||||||
@ -12,4 +12,10 @@ interface RecipeInfosView {
|
|||||||
content: RecipeInfoView[]
|
content: RecipeInfoView[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toRecipeInfosView = ({ pageNumber, pageSize, content }: RawRecipeInfosView): RecipeInfosView => ({
|
||||||
|
pageNumber,
|
||||||
|
pageSize,
|
||||||
|
content: content.map(toRecipeInfoView)
|
||||||
|
})
|
||||||
|
|
||||||
export default RecipeInfosView
|
export default RecipeInfosView
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useLocation, useNavigate } from '@tanstack/react-router'
|
import { useLocation, useNavigate } from '@tanstack/react-router'
|
||||||
import { useAuth } from '../../auth'
|
import { useAuth } from '../../AuthProvider'
|
||||||
import classes from './header.module.css'
|
import classes from './header.module.css'
|
||||||
|
|
||||||
export interface HeaderProps {
|
export interface HeaderProps {
|
||||||
|
38
src/main.tsx
38
src/main.tsx
@ -1,12 +1,16 @@
|
|||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { fas } from '@fortawesome/free-solid-svg-icons'
|
import { fas } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||||
import { Router, RouterProvider, createRouter } from '@tanstack/react-router'
|
import { Router, RouterProvider, createRouter } from '@tanstack/react-router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import { AuthProvider, useAuth } from './auth'
|
import { ApiError } from './api/ApiError'
|
||||||
import AuthAwareQueryClientProvider from './AuthAwareQueryClientProvider'
|
import ExpiredTokenError from './api/ExpiredTokenError'
|
||||||
|
import { AuthProvider, useAuth } from './AuthProvider'
|
||||||
import './main.css'
|
import './main.css'
|
||||||
import { routeTree } from './routeTree.gen'
|
import { routeTree } from './routeTree.gen'
|
||||||
|
import RefreshProvider from './RefreshProvider'
|
||||||
|
|
||||||
// Font-Awesome: load icons
|
// Font-Awesome: load icons
|
||||||
library.add(fas)
|
library.add(fas)
|
||||||
@ -27,13 +31,41 @@ declare module '@tanstack/react-router' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry(failureCount, error) {
|
||||||
|
if (error instanceof ExpiredTokenError || (error instanceof ApiError && error.status === 404)) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return failureCount <= 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
retryDelay(failureCount, error) {
|
||||||
|
if (error instanceof ExpiredTokenError) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return failureCount * 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const InnerApp = () => {
|
const InnerApp = () => {
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
return (
|
return (
|
||||||
<RouterProvider
|
<RouterProvider
|
||||||
router={router}
|
router={router}
|
||||||
context={{ auth }}
|
context={{ auth }}
|
||||||
InnerWrap={({ children }) => <AuthAwareQueryClientProvider>{children}</AuthAwareQueryClientProvider>}
|
InnerWrap={({ children }) => (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<RefreshProvider>
|
||||||
|
{children}
|
||||||
|
<ReactQueryDevtools position="right" buttonPosition="top-right" />
|
||||||
|
</RefreshProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
)}
|
||||||
></RouterProvider>
|
></RouterProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { ApiError } from '../../api/ApiError'
|
|||||||
import getRecipe from '../../api/getRecipe'
|
import getRecipe from '../../api/getRecipe'
|
||||||
import UpdateRecipeSpec, { fromFullRecipeView } from '../../api/types/UpdateRecipeSpec'
|
import UpdateRecipeSpec, { fromFullRecipeView } from '../../api/types/UpdateRecipeSpec'
|
||||||
import updateRecipe from '../../api/updateRecipe'
|
import updateRecipe from '../../api/updateRecipe'
|
||||||
import { useAuth } from '../../auth'
|
import { useAuth } from '../../AuthProvider'
|
||||||
import classes from './edit-recipe.module.css'
|
import classes from './edit-recipe.module.css'
|
||||||
|
|
||||||
interface ControlProps {
|
interface ControlProps {
|
||||||
|
@ -6,7 +6,7 @@ import getImage from '../../api/getImage'
|
|||||||
import getRecipe from '../../api/getRecipe'
|
import getRecipe from '../../api/getRecipe'
|
||||||
import removeStar from '../../api/removeStar'
|
import removeStar from '../../api/removeStar'
|
||||||
import GetRecipeView from '../../api/types/GetRecipeView'
|
import GetRecipeView from '../../api/types/GetRecipeView'
|
||||||
import { useAuth } from '../../auth'
|
import { useAuth } from '../../AuthProvider'
|
||||||
import RecipeVisibilityIcon from '../../components/recipe-visibility-icon/RecipeVisibilityIcon'
|
import RecipeVisibilityIcon from '../../components/recipe-visibility-icon/RecipeVisibilityIcon'
|
||||||
import UserIconAndName from '../../components/user-icon-and-name/UserIconAndName'
|
import UserIconAndName from '../../components/user-icon-and-name/UserIconAndName'
|
||||||
import classes from './recipe.module.css'
|
import classes from './recipe.module.css'
|
||||||
|
@ -3,15 +3,17 @@ import { useState } from 'react'
|
|||||||
import { ApiError } from '../../api/ApiError'
|
import { ApiError } from '../../api/ApiError'
|
||||||
import getImage from '../../api/getImage'
|
import getImage from '../../api/getImage'
|
||||||
import getRecipeInfos from '../../api/getRecipeInfos'
|
import getRecipeInfos from '../../api/getRecipeInfos'
|
||||||
import { useAuth } from '../../auth'
|
import { useAuth } from '../../AuthProvider'
|
||||||
import RecipeCard from '../../components/recipe-card/RecipeCard'
|
import RecipeCard from '../../components/recipe-card/RecipeCard'
|
||||||
import classes from './recipes.module.css'
|
import classes from './recipes.module.css'
|
||||||
|
import { useRefresh } from '../../RefreshProvider'
|
||||||
|
|
||||||
const Recipes = () => {
|
const Recipes = () => {
|
||||||
const [pageNumber, setPageNumber] = useState(0)
|
const [pageNumber, setPageNumber] = useState(0)
|
||||||
const [pageSize, setPageSize] = useState(20)
|
const [pageSize, setPageSize] = useState(20)
|
||||||
|
|
||||||
const { accessToken } = useAuth()
|
const { accessToken } = useAuth()
|
||||||
|
const refresh = useRefresh()
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { data, isPending, error } = useQuery(
|
const { data, isPending, error } = useQuery(
|
||||||
@ -19,10 +21,11 @@ const Recipes = () => {
|
|||||||
queryKey: ['recipeInfos'],
|
queryKey: ['recipeInfos'],
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
getRecipeInfos({
|
getRecipeInfos({
|
||||||
abortSignal: signal,
|
accessToken,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
pageSize,
|
pageSize,
|
||||||
accessToken
|
refresh,
|
||||||
|
signal
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
queryClient
|
queryClient
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
||||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
|
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
|
||||||
import RouterContext from '../RouterContext'
|
import RouterContext from '../RouterContext'
|
||||||
import { useAuth } from '../auth'
|
import { useAuth } from '../AuthProvider'
|
||||||
import Footer from '../components/footer/Footer'
|
import Footer from '../components/footer/Footer'
|
||||||
import Header from '../components/header/Header'
|
import Header from '../components/header/Header'
|
||||||
import classes from './__root.module.css'
|
import classes from './__root.module.css'
|
||||||
|
@ -2,7 +2,7 @@ import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
|
|||||||
import { FormEvent, useState } from 'react'
|
import { FormEvent, useState } from 'react'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import login from '../api/login'
|
import login from '../api/login'
|
||||||
import { useAuth } from '../auth'
|
import { useAuth } from '../AuthProvider'
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const { putToken } = useAuth()
|
const { putToken } = useAuth()
|
||||||
|
7
src/types/Refresh.ts
Normal file
7
src/types/Refresh.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import AccessToken from './AccessToken'
|
||||||
|
|
||||||
|
interface Refresh {
|
||||||
|
(): Promise<AccessToken | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Refresh
|
Loading…
Reference in New Issue
Block a user