feat: added redux
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { Provider } from 'react-redux'
|
||||
import { store } from './store'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux'
|
||||
import artistsReducer from './slices/artistsSlice'
|
||||
import albumsReducer from './slices/albumsSlice'
|
||||
import albumTracksReducer from './slices/albumTracksSlice'
|
||||
import trackDetailReducer from './slices/trackDetailSlice'
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
artists: artistsReducer,
|
||||
albums: albumsReducer,
|
||||
albumTracks: albumTracksReducer,
|
||||
trackDetail: trackDetailReducer,
|
||||
},
|
||||
})
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import type { Track } from '../../types'
|
||||
import { getAlbumTracks } from '../../furumiApi'
|
||||
|
||||
export const fetchAlbumTracks = createAsyncThunk(
|
||||
'albumTracks/fetch',
|
||||
async (albumSlug: string, { rejectWithValue }) => {
|
||||
const data = await getAlbumTracks(albumSlug)
|
||||
if (data === null) return rejectWithValue('Failed to fetch album tracks')
|
||||
return { albumSlug, tracks: data }
|
||||
},
|
||||
)
|
||||
|
||||
interface AlbumTracksState {
|
||||
byAlbum: Record<string, Track[]>
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
const initialState: AlbumTracksState = {
|
||||
byAlbum: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
}
|
||||
|
||||
const albumTracksSlice = createSlice({
|
||||
name: 'albumTracks',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearAlbumTracks(state) {
|
||||
state.byAlbum = {}
|
||||
state.error = null
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchAlbumTracks.pending, (state) => {
|
||||
state.loading = true
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchAlbumTracks.fulfilled, (state, action) => {
|
||||
state.loading = false
|
||||
state.byAlbum[action.payload.albumSlug] = action.payload.tracks
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchAlbumTracks.rejected, (state, action) => {
|
||||
state.loading = false
|
||||
state.error = action.payload as string ?? 'Unknown error'
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const { clearAlbumTracks } = albumTracksSlice.actions
|
||||
export default albumTracksSlice.reducer
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import type { Album } from '../../types'
|
||||
import { getArtistAlbums } from '../../furumiApi'
|
||||
|
||||
export const fetchArtistAlbums = createAsyncThunk(
|
||||
'albums/fetchByArtist',
|
||||
async (artistSlug: string, { rejectWithValue }) => {
|
||||
const data = await getArtistAlbums(artistSlug)
|
||||
if (data === null) return rejectWithValue('Failed to fetch albums')
|
||||
return { artistSlug, albums: data }
|
||||
},
|
||||
)
|
||||
|
||||
interface AlbumsState {
|
||||
byArtist: Record<string, Album[]>
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
const initialState: AlbumsState = {
|
||||
byArtist: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
}
|
||||
|
||||
const albumsSlice = createSlice({
|
||||
name: 'albums',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearAlbums(state) {
|
||||
state.byArtist = {}
|
||||
state.error = null
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchArtistAlbums.pending, (state) => {
|
||||
state.loading = true
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchArtistAlbums.fulfilled, (state, action) => {
|
||||
state.loading = false
|
||||
state.byArtist[action.payload.artistSlug] = action.payload.albums
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchArtistAlbums.rejected, (state, action) => {
|
||||
state.loading = false
|
||||
state.error = action.payload as string ?? 'Unknown error'
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const { clearAlbums } = albumsSlice.actions
|
||||
export default albumsSlice.reducer
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import type { Artist } from '../../types'
|
||||
import { getArtists } from '../../furumiApi'
|
||||
|
||||
export const fetchArtists = createAsyncThunk(
|
||||
'artists/fetch',
|
||||
async (_, { rejectWithValue }) => {
|
||||
const data = await getArtists()
|
||||
if (data === null) return rejectWithValue('Failed to fetch artists')
|
||||
return data
|
||||
},
|
||||
)
|
||||
|
||||
interface ArtistsState {
|
||||
items: Artist[]
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
const initialState: ArtistsState = {
|
||||
items: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
}
|
||||
|
||||
const artistsSlice = createSlice({
|
||||
name: 'artists',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearArtists(state) {
|
||||
state.items = []
|
||||
state.error = null
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchArtists.pending, (state) => {
|
||||
state.loading = true
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchArtists.fulfilled, (state, action) => {
|
||||
state.loading = false
|
||||
state.items = action.payload
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchArtists.rejected, (state, action) => {
|
||||
state.loading = false
|
||||
state.error = action.payload as string ?? 'Unknown error'
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const { clearArtists } = artistsSlice.actions
|
||||
export default artistsSlice.reducer
|
||||
@@ -0,0 +1,57 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import type { TrackDetail } from '../../types'
|
||||
import { getTrackInfo } from '../../furumiApi'
|
||||
|
||||
export const fetchTrackDetail = createAsyncThunk(
|
||||
'trackDetail/fetch',
|
||||
async (trackSlug: string, { rejectWithValue }) => {
|
||||
const data = await getTrackInfo(trackSlug)
|
||||
if (data === null) return rejectWithValue('Failed to fetch track detail')
|
||||
return { trackSlug, detail: data }
|
||||
},
|
||||
)
|
||||
|
||||
interface TrackDetailState {
|
||||
bySlug: Record<string, TrackDetail>
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
const initialState: TrackDetailState = {
|
||||
bySlug: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
}
|
||||
|
||||
const trackDetailSlice = createSlice({
|
||||
name: 'trackDetail',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearTrackDetail(state) {
|
||||
state.bySlug = {}
|
||||
state.error = null
|
||||
},
|
||||
removeTrackDetail(state, action: { payload: string }) {
|
||||
delete state.bySlug[action.payload]
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchTrackDetail.pending, (state) => {
|
||||
state.loading = true
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchTrackDetail.fulfilled, (state, action) => {
|
||||
state.loading = false
|
||||
state.bySlug[action.payload.trackSlug] = action.payload.detail
|
||||
state.error = null
|
||||
})
|
||||
.addCase(fetchTrackDetail.rejected, (state, action) => {
|
||||
state.loading = false
|
||||
state.error = action.payload as string ?? 'Unknown error'
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const { clearTrackDetail, removeTrackDetail } = trackDetailSlice.actions
|
||||
export default trackDetailSlice.reducer
|
||||
Reference in New Issue
Block a user