1 - init project

This commit is contained in:
safronman
2024-06-13 14:14:16 +03:00
parent 956a8faca1
commit ce9d06057e
8 changed files with 24 additions and 131 deletions

View File

@@ -1,5 +1,3 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './styles/index.scss'
@@ -8,8 +6,4 @@ import '@fontsource/roboto/700.css'
import { App } from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
)
createRoot(document.getElementById('root')!).render(<App />)

View File

@@ -1,7 +1,7 @@
import { baseApi } from '..'
import { flashcardsApi } from '..'
import { LoginArgs, User } from './auth.types'
export const authService = baseApi.injectEndpoints({
export const authService = flashcardsApi.injectEndpoints({
endpoints: builder => ({
login: builder.mutation<void, LoginArgs>({
invalidatesTags: ['Me'],

View File

@@ -1,9 +0,0 @@
import { baseQueryWithReauth } from '@/services/base-query-with-reauth'
import { createApi } from '@reduxjs/toolkit/query/react'
export const baseApi = createApi({
baseQuery: baseQueryWithReauth,
endpoints: () => ({}),
reducerPath: 'baseApi',
tagTypes: ['Decks', 'Me'],
})

View File

@@ -1,47 +0,0 @@
import {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
fetchBaseQuery,
} from '@reduxjs/toolkit/query/react'
import { Mutex } from 'async-mutex'
const mutex = new Mutex()
const baseQuery = fetchBaseQuery({
baseUrl: 'https://api.flashcards.andrii.es',
credentials: 'include',
})
export const baseQueryWithReauth: BaseQueryFn<
FetchArgs | string,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
await mutex.waitForUnlock()
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
if (!mutex.isLocked()) {
const release = await mutex.acquire()
// try to get a new token
const refreshResult = await baseQuery(
{ method: 'POST', url: '/v1/auth/refresh-token' },
api,
extraOptions
)
if (refreshResult.meta?.response?.status === 204) {
// retry the initial query
result = await baseQuery(args, api, extraOptions)
}
release()
} else {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
result = await baseQuery(args, api, extraOptions)
}
}
return result
}

View File

@@ -5,32 +5,15 @@ import {
DecksResponse,
GetDecksArgs,
UpdateDeckArgs,
baseApi,
} from '@/services'
import { RootState } from '@/services/store'
import { getValuable } from '@/utils'
const decksService = baseApi.injectEndpoints({
import { flashcardsApi } from '../flashcardsApi'
const decksService = flashcardsApi.injectEndpoints({
endpoints: builder => ({
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
invalidatesTags: ['Decks'],
async onQueryStarted(_, { dispatch, getState, queryFulfilled }) {
const res = await queryFulfilled
for (const { endpointName, originalArgs } of decksService.util.selectInvalidatedBy(
getState(),
[{ type: 'Decks' }]
)) {
if (endpointName !== 'getDecks') {
continue
}
dispatch(
decksService.util.updateQueryData(endpointName, originalArgs, draft => {
draft.items.unshift(res.data)
})
)
}
},
query: body => ({
body,
method: 'POST',
@@ -61,42 +44,6 @@ const decksService = baseApi.injectEndpoints({
}),
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
invalidatesTags: ['Decks'],
async onQueryStarted({ id, ...patch }, { dispatch, getState, queryFulfilled }) {
const state = getState() as RootState
const minCardsCount = state.decks.minCards
const search = state.decks.search
const currentPage = state.decks.currentPage
const maxCardsCount = state.decks.maxCards
const authorId = state.decks.authorId
const patchResult = dispatch(
decksService.util.updateQueryData(
'getDecks',
{
authorId,
currentPage,
maxCardsCount,
minCardsCount,
name: search,
},
draft => {
const deck = draft.items.find(deck => deck.id === id)
if (!deck) {
return
}
Object.assign(deck, patch)
}
)
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
},
query: ({ id, ...body }) => ({
body,
method: 'PATCH',

View File

@@ -0,0 +1,14 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const flashcardsApi = createApi({
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.flashcards.andrii.es',
credentials: 'include',
prepareHeaders: headers => {
headers.append('x-auth-skip', 'true')
},
}),
endpoints: () => ({}),
reducerPath: 'flashcardsApi',
tagTypes: ['Decks', 'Me'],
})

View File

@@ -1,2 +1,2 @@
export * from './base-api'
export * from './decks'
export { flashcardsApi } from './flashcardsApi'

View File

@@ -1,24 +1,18 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import { decksSlice } from '@/services/decks/decks.slice'
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
import { baseApi } from './base-api'
import { flashcardsApi } from './flashcardsApi'
export const store = configureStore({
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(baseApi.middleware),
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(flashcardsApi.middleware),
reducer: {
[baseApi.reducerPath]: baseApi.reducer,
[decksSlice.name]: decksSlice.reducer,
[flashcardsApi.reducerPath]: flashcardsApi.reducer,
},
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
setupListeners(store.dispatch)