mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-18 05:09:23 +00:00
1 - init project
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
import { StrictMode } from 'react'
|
|
||||||
|
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
|
||||||
import './styles/index.scss'
|
import './styles/index.scss'
|
||||||
@@ -8,8 +6,4 @@ import '@fontsource/roboto/700.css'
|
|||||||
|
|
||||||
import { App } from './App'
|
import { App } from './App'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(<App />)
|
||||||
<StrictMode>
|
|
||||||
<App />
|
|
||||||
</StrictMode>
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { baseApi } from '..'
|
import { flashcardsApi } from '..'
|
||||||
import { LoginArgs, User } from './auth.types'
|
import { LoginArgs, User } from './auth.types'
|
||||||
|
|
||||||
export const authService = baseApi.injectEndpoints({
|
export const authService = flashcardsApi.injectEndpoints({
|
||||||
endpoints: builder => ({
|
endpoints: builder => ({
|
||||||
login: builder.mutation<void, LoginArgs>({
|
login: builder.mutation<void, LoginArgs>({
|
||||||
invalidatesTags: ['Me'],
|
invalidatesTags: ['Me'],
|
||||||
|
|||||||
@@ -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'],
|
|
||||||
})
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -5,32 +5,15 @@ import {
|
|||||||
DecksResponse,
|
DecksResponse,
|
||||||
GetDecksArgs,
|
GetDecksArgs,
|
||||||
UpdateDeckArgs,
|
UpdateDeckArgs,
|
||||||
baseApi,
|
|
||||||
} from '@/services'
|
} from '@/services'
|
||||||
import { RootState } from '@/services/store'
|
|
||||||
import { getValuable } from '@/utils'
|
import { getValuable } from '@/utils'
|
||||||
|
|
||||||
const decksService = baseApi.injectEndpoints({
|
import { flashcardsApi } from '../flashcardsApi'
|
||||||
|
|
||||||
|
const decksService = flashcardsApi.injectEndpoints({
|
||||||
endpoints: builder => ({
|
endpoints: builder => ({
|
||||||
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
|
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
|
||||||
invalidatesTags: ['Decks'],
|
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 => ({
|
query: body => ({
|
||||||
body,
|
body,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -61,42 +44,6 @@ const decksService = baseApi.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
|
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
|
||||||
invalidatesTags: ['Decks'],
|
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 }) => ({
|
query: ({ id, ...body }) => ({
|
||||||
body,
|
body,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
|||||||
14
src/services/flashcardsApi.ts
Normal file
14
src/services/flashcardsApi.ts
Normal 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'],
|
||||||
|
})
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './base-api'
|
|
||||||
export * from './decks'
|
export * from './decks'
|
||||||
|
export { flashcardsApi } from './flashcardsApi'
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import { decksSlice } from '@/services/decks/decks.slice'
|
import { decksSlice } from '@/services/decks/decks.slice'
|
||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
import { setupListeners } from '@reduxjs/toolkit/query/react'
|
import { setupListeners } from '@reduxjs/toolkit/query/react'
|
||||||
|
|
||||||
import { baseApi } from './base-api'
|
import { flashcardsApi } from './flashcardsApi'
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(baseApi.middleware),
|
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(flashcardsApi.middleware),
|
||||||
reducer: {
|
reducer: {
|
||||||
[baseApi.reducerPath]: baseApi.reducer,
|
|
||||||
[decksSlice.name]: decksSlice.reducer,
|
[decksSlice.name]: decksSlice.reducer,
|
||||||
|
[flashcardsApi.reducerPath]: flashcardsApi.reducer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AppDispatch = typeof store.dispatch
|
export type AppDispatch = typeof store.dispatch
|
||||||
export type RootState = ReturnType<typeof store.getState>
|
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)
|
setupListeners(store.dispatch)
|
||||||
|
|||||||
Reference in New Issue
Block a user