Migrating from Redux to RTK-query
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"@fortawesome/fontawesome-free": "^6.3.0",
|
||||
"@redux-devtools/extension": "^3.2.5",
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@tanstack/react-table": "^8.9.3",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
SETTINGS_CALL_IN_PROGRESS,
|
||||
SETTINGS_DB_FLUSH_SUCCESS,
|
||||
SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED,
|
||||
} from "../constants/action-types";
|
||||
} from "../reducers/settings.reducer";
|
||||
import {
|
||||
LIBRARY_SERVICE_BASE_URI,
|
||||
SETTINGS_SERVICE_BASE_URI,
|
||||
@@ -72,20 +72,19 @@ export const flushDb = () => async (dispatch) => {
|
||||
};
|
||||
|
||||
export const getQBitTorrentClientInfo = (hostInfo) => async (dispatch) => {
|
||||
const foo = await axios.request({
|
||||
await axios.request({
|
||||
url: `${QBITTORRENT_SERVICE_BASE_URI}/connect`,
|
||||
method: "POST",
|
||||
data: hostInfo,
|
||||
});
|
||||
const bar = await axios.request({
|
||||
const qBittorrentClientInfo = await axios.request({
|
||||
url: `${QBITTORRENT_SERVICE_BASE_URI}/getClientInfo`,
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
console.log(bar);
|
||||
dispatch({
|
||||
type: SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED,
|
||||
data: bar.data,
|
||||
data: qBittorrentClientInfo.data,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -119,7 +119,6 @@ export const App = (): ReactElement => {
|
||||
<AirDCPPSocketContextProvider>
|
||||
<div>
|
||||
<AirDCPPSocketComponent />
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/import" element={<Import path={"./comics"} />} />
|
||||
|
||||
@@ -16,110 +16,50 @@ import Header from "../shared/Header";
|
||||
|
||||
export const Dashboard = (): ReactElement => {
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(fetchVolumeGroups());
|
||||
dispatch(
|
||||
getComicBooks({
|
||||
paginationOptions: {
|
||||
page: 0,
|
||||
limit: 5,
|
||||
sort: { updatedAt: "-1" },
|
||||
},
|
||||
predicate: { "acquisition.source.wanted": false },
|
||||
comicStatus: "recent",
|
||||
}),
|
||||
);
|
||||
dispatch(
|
||||
getComicBooks({
|
||||
paginationOptions: {
|
||||
page: 0,
|
||||
limit: 5,
|
||||
sort: { updatedAt: "-1" },
|
||||
},
|
||||
predicate: { "acquisition.source.wanted": true },
|
||||
comicStatus: "wanted",
|
||||
}),
|
||||
);
|
||||
dispatch(getLibraryStatistics());
|
||||
}, []);
|
||||
|
||||
const recentComics = useSelector(
|
||||
(state: RootState) => state.fileOps.recentComics,
|
||||
);
|
||||
const wantedComics = useSelector(
|
||||
(state: RootState) => state.fileOps.wantedComics,
|
||||
);
|
||||
const volumeGroups = useSelector(
|
||||
(state: RootState) => state.fileOps.comicVolumeGroups,
|
||||
);
|
||||
|
||||
const libraryStatistics = useSelector(
|
||||
(state: RootState) => state.comicInfo.libraryStatistics,
|
||||
);
|
||||
// useEffect(() => {
|
||||
// dispatch(fetchVolumeGroups());
|
||||
// dispatch(
|
||||
// getComicBooks({
|
||||
// paginationOptions: {
|
||||
// page: 0,
|
||||
// limit: 5,
|
||||
// sort: { updatedAt: "-1" },
|
||||
// },
|
||||
// predicate: { "acquisition.source.wanted": false },
|
||||
// comicStatus: "recent",
|
||||
// }),
|
||||
// );
|
||||
// dispatch(
|
||||
// getComicBooks({
|
||||
// paginationOptions: {
|
||||
// page: 0,
|
||||
// limit: 5,
|
||||
// sort: { updatedAt: "-1" },
|
||||
// },
|
||||
// predicate: { "acquisition.source.wanted": true },
|
||||
// comicStatus: "wanted",
|
||||
// }),
|
||||
// );
|
||||
// dispatch(getLibraryStatistics());
|
||||
// }, []);
|
||||
//
|
||||
// const recentComics = useSelector(
|
||||
// (state: RootState) => state.fileOps.recentComics,
|
||||
// );
|
||||
// const wantedComics = useSelector(
|
||||
// (state: RootState) => state.fileOps.wantedComics,
|
||||
// );
|
||||
// const volumeGroups = useSelector(
|
||||
// (state: RootState) => state.fileOps.comicVolumeGroups,
|
||||
// );
|
||||
//
|
||||
// const libraryStatistics = useSelector(
|
||||
// (state: RootState) => state.comicInfo.libraryStatistics,
|
||||
// );
|
||||
return (
|
||||
<div className="container">
|
||||
<section className="section">
|
||||
<h1 className="title">Dashboard</h1>
|
||||
|
||||
{!isEmpty(recentComics) ? (
|
||||
<>
|
||||
{/* Pull List */}
|
||||
<PullList issues={recentComics} />
|
||||
<>
|
||||
<div className="content mt-6">
|
||||
<Header
|
||||
headerContent="Import Activity"
|
||||
subHeaderContent="Results aggregated from the last import"
|
||||
iconClassNames="fa-solid fa-file-invoice mr-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<abbr title="Position">Pos</abbr>
|
||||
</th>
|
||||
<th>Team</th>
|
||||
<th>
|
||||
<abbr title="Played">Pld</abbr>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1</th>
|
||||
<td>38</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
|
||||
{/* Stats */}
|
||||
{!isEmpty(libraryStatistics) && (
|
||||
<LibraryStatistics stats={libraryStatistics} />
|
||||
)}
|
||||
{/* Wanted comics */}
|
||||
{!isEmpty(wantedComics) && (
|
||||
<WantedComicsList comics={wantedComics} />
|
||||
)}
|
||||
{/* Recent imports */}
|
||||
<RecentlyImported comicBookCovers={recentComics} />
|
||||
|
||||
{/* Volumes */}
|
||||
{!isEmpty(volumeGroups) && (
|
||||
<VolumeGroups volumeGroups={volumeGroups} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<ZeroState
|
||||
header={"Set the source directory"}
|
||||
message={
|
||||
"No comics were found! Please point ThreeTwo! to a directory..."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,11 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectionForm initialData={initFormData} submitHandler={onSubmit} />
|
||||
<ConnectionForm
|
||||
initialData={initFormData}
|
||||
submitHandler={onSubmit}
|
||||
formHeading={"Configure AirDC++"}
|
||||
/>
|
||||
|
||||
{!isEmpty(airDCPPSettings.airDCPPState.socketConnectionInformation) ? (
|
||||
<AirDCPPSettingsConfirmation
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
import React, { ReactElement, useCallback, useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useAppDispatch, useAppSelector } from "../../../hooks/store";
|
||||
import { getQBitTorrentClientInfo } from "../../../actions/settings.actions";
|
||||
import { saveSettings } from "../../../actions/settings.actions";
|
||||
import { ConnectionForm } from "../../shared/ConnectionForm/ConnectionForm";
|
||||
import { useGetAllSettingsQuery } from "../../../services/settings.api";
|
||||
import { isUndefined } from "lodash";
|
||||
import { useConnectToQBittorrentClientQuery } from "../../../services/torrents.api";
|
||||
|
||||
export const QbittorrentConnectionForm = (): ReactElement => {
|
||||
const dispatch = useDispatch();
|
||||
const torrents = useSelector(
|
||||
(state: RootState) => state.settings.torrentsList,
|
||||
);
|
||||
|
||||
const qBittorrentSettings = useSelector((state: RootState) => {
|
||||
if (!isUndefined(state.settings.data.bittorrent)) {
|
||||
return state.settings.data.bittorrent.client.host;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUndefined(qBittorrentSettings)) {
|
||||
dispatch(getQBitTorrentClientInfo(qBittorrentSettings));
|
||||
}
|
||||
}, []);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onSubmit = useCallback(async (values) => {
|
||||
try {
|
||||
@@ -31,15 +18,7 @@ export const QbittorrentConnectionForm = (): ReactElement => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectionForm
|
||||
initialData={qBittorrentSettings}
|
||||
submitHandler={onSubmit}
|
||||
/>
|
||||
<pre> {torrents && JSON.stringify(torrents, null, 4)} </pre>
|
||||
</>
|
||||
);
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default QbittorrentConnectionForm;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { isEmpty } from "lodash";
|
||||
export const ConnectionForm = ({
|
||||
initialData,
|
||||
submitHandler,
|
||||
formHeading,
|
||||
}): ReactElement => {
|
||||
return (
|
||||
<>
|
||||
@@ -14,8 +15,8 @@ export const ConnectionForm = ({
|
||||
initialValues={initialData}
|
||||
render={({ handleSubmit }) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h2>Configure AirDC++</h2>
|
||||
<label className="label">AirDC++ Hostname</label>
|
||||
<h2>{formHeading}</h2>
|
||||
<label className="label">Hostname</label>
|
||||
<div className="field has-addons">
|
||||
<p className="control">
|
||||
<span className="select">
|
||||
|
||||
6
src/client/hooks/store.ts
Normal file
6
src/client/hooks/store.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
import type { RootState, AppDispatch } from "../store";
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
@@ -1,11 +1,11 @@
|
||||
import comicinfoReducer from "../reducers/comicinfo.reducer";
|
||||
import fileOpsReducer from "../reducers/fileops.reducer";
|
||||
import airdcppReducer from "../reducers/airdcpp.reducer";
|
||||
import settingsReducer from "../reducers/settings.reducer";
|
||||
// import settingsReducer from "../reducers/settings.reducer";
|
||||
|
||||
export const reducers = {
|
||||
comicInfo: comicinfoReducer,
|
||||
fileOps: fileOpsReducer,
|
||||
airdcpp: airdcppReducer,
|
||||
settings: settingsReducer,
|
||||
// settings: settingsReducer,
|
||||
};
|
||||
|
||||
@@ -1,57 +1,67 @@
|
||||
import {
|
||||
SETTINGS_CALL_FAILED,
|
||||
SETTINGS_OBJECT_FETCHED,
|
||||
SETTINGS_OBJECT_DELETED,
|
||||
SETTINGS_CALL_IN_PROGRESS,
|
||||
SETTINGS_DB_FLUSH_SUCCESS,
|
||||
SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED,
|
||||
} from "../constants/action-types";
|
||||
const initialState = {
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
import { RootState } from "../store";
|
||||
import { isUndefined } from "lodash";
|
||||
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
|
||||
|
||||
export interface InitialState {
|
||||
data: object;
|
||||
inProgress: boolean;
|
||||
dbFlushed: boolean;
|
||||
torrentsList: Array<any>;
|
||||
}
|
||||
const initialState: InitialState = {
|
||||
data: {},
|
||||
inProgress: false,
|
||||
DbFlushed: false,
|
||||
dbFlushed: false,
|
||||
torrentsList: [],
|
||||
};
|
||||
|
||||
function settingsReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case SETTINGS_CALL_IN_PROGRESS:
|
||||
return {
|
||||
...state,
|
||||
inProgress: true,
|
||||
};
|
||||
export const settingsSlice = createSlice({
|
||||
name: "settings",
|
||||
initialState,
|
||||
reducers: {
|
||||
SETTINGS_CALL_IN_PROGRESS: (state) => {
|
||||
state.inProgress = true;
|
||||
},
|
||||
|
||||
case SETTINGS_OBJECT_FETCHED:
|
||||
return {
|
||||
...state,
|
||||
data: action.data,
|
||||
inProgress: false,
|
||||
};
|
||||
SETTINGS_OBJECT_FETCHED: (state, action) => {
|
||||
state.data = action.payload;
|
||||
state.inProgress = false;
|
||||
},
|
||||
|
||||
case SETTINGS_OBJECT_DELETED:
|
||||
return {
|
||||
...state,
|
||||
data: action.data,
|
||||
inProgress: false,
|
||||
};
|
||||
SETTINGS_OBJECT_DELETED: (state, action) => {
|
||||
state.data = action.payload;
|
||||
state.inProgress = false;
|
||||
},
|
||||
|
||||
case SETTINGS_DB_FLUSH_SUCCESS:
|
||||
SETTINGS_DB_FLUSH_SUCCESS: (state, action) => {
|
||||
state.dbFlushed = action.payload;
|
||||
state.inProgress = false;
|
||||
},
|
||||
|
||||
SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED: (state, action) => {
|
||||
console.log(state);
|
||||
return {
|
||||
...state,
|
||||
DbFlushed: action.data,
|
||||
inProgress: false,
|
||||
};
|
||||
|
||||
case SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED:
|
||||
return {
|
||||
...state,
|
||||
torrentsList: action.data,
|
||||
}
|
||||
console.log(action);
|
||||
state.torrentsList = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
default:
|
||||
return { ...state };
|
||||
export const {
|
||||
SETTINGS_CALL_IN_PROGRESS,
|
||||
SETTINGS_OBJECT_FETCHED,
|
||||
SETTINGS_OBJECT_DELETED,
|
||||
SETTINGS_DB_FLUSH_SUCCESS,
|
||||
SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED,
|
||||
} = settingsSlice.actions;
|
||||
|
||||
// Other code such as selectors can use the imported `RootState` type
|
||||
export const torrentsList = (state: RootState) => state.settings.torrentsList;
|
||||
export const qBittorrentSettings = (state: RootState) => {
|
||||
console.log(state);
|
||||
if (!isUndefined(state.settings?.data?.bittorrent)) {
|
||||
return state.settings?.data?.bittorrent.client.host;
|
||||
}
|
||||
}
|
||||
|
||||
export default settingsReducer;
|
||||
};
|
||||
export default settingsSlice.reducer;
|
||||
|
||||
0
src/client/services/acquisition.api.ts
Normal file
0
src/client/services/acquisition.api.ts
Normal file
7
src/client/services/empty.api.ts
Normal file
7
src/client/services/empty.api.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
// initialize an empty api service that we'll inject endpoints into later as needed
|
||||
export const emptySplitApi = createApi({
|
||||
baseQuery: fetchBaseQuery({ baseUrl: "http://" }),
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
19
src/client/services/settings.api.ts
Normal file
19
src/client/services/settings.api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { emptySplitApi } from "./empty.api";
|
||||
import { useConnectToQBittorrentClientQuery } from "./torrents.api";
|
||||
|
||||
export const settingsApi = emptySplitApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getAllSettings: builder.query({
|
||||
query: () => "localhost:3000/api/settings/getAllSettings",
|
||||
}),
|
||||
getQBitTorrentClientInfo: builder.query({
|
||||
queryFn: async () => {
|
||||
try {
|
||||
} catch (err) {}
|
||||
},
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const { useGetAllSettingsQuery } = settingsApi;
|
||||
27
src/client/services/torrents.api.ts
Normal file
27
src/client/services/torrents.api.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { emptySplitApi } from "./empty.api";
|
||||
|
||||
export const torrentsApi = emptySplitApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
connectToQBittorrentClient: builder.query({
|
||||
queryFn: async (_arg, _queryApi, _extraOptions, fetchWithBQ) => {
|
||||
const qBittorrentHostInfo = await fetchWithBQ(
|
||||
"localhost:3000/api/settings/getAllSettings",
|
||||
);
|
||||
await fetchWithBQ({
|
||||
url: "localhost:3060/api/qbittorrent/connect",
|
||||
method: "POST",
|
||||
data: qBittorrentHostInfo,
|
||||
});
|
||||
console.log(qBittorrentHostInfo);
|
||||
return {
|
||||
url: "",
|
||||
method: "GET",
|
||||
data: qBittorrentHostInfo,
|
||||
};
|
||||
},
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const { useConnectToQBittorrentClientQuery } = torrentsApi;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createStore, combineReducers, applyMiddleware } from "redux";
|
||||
import { createHashHistory } from "history";
|
||||
import { composeWithDevTools } from "@redux-devtools/extension";
|
||||
import thunk from "redux-thunk";
|
||||
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
|
||||
import { createReduxHistoryContext } from "redux-first-history";
|
||||
import { reducers } from "../reducers/index";
|
||||
import socketIoMiddleware from "redux-socket.io-middleware";
|
||||
import socketIOMiddleware from "../shared/middleware/SocketIOMiddleware";
|
||||
import socketIOConnectionInstance from "../shared/socket.io/instance";
|
||||
import settingsReducer from "../reducers/settings.reducer";
|
||||
import { settingsApi } from "../services/settings.api";
|
||||
|
||||
const customSocketIOMiddleware = socketIOMiddleware(socketIOConnectionInstance);
|
||||
|
||||
@@ -15,19 +15,30 @@ const { createReduxHistory, routerMiddleware, routerReducer } =
|
||||
history: createHashHistory(),
|
||||
});
|
||||
|
||||
export const store = createStore(
|
||||
combineReducers({
|
||||
router: routerReducer,
|
||||
...reducers,
|
||||
}),
|
||||
composeWithDevTools(
|
||||
applyMiddleware(
|
||||
socketIoMiddleware(socketIOConnectionInstance),
|
||||
customSocketIOMiddleware,
|
||||
thunk,
|
||||
routerMiddleware,
|
||||
),
|
||||
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
|
||||
),
|
||||
);
|
||||
const rootReducer = (history) => ({
|
||||
settings: settingsReducer,
|
||||
[settingsApi.reducerPath]: settingsApi.reducer,
|
||||
router: routerReducer,
|
||||
});
|
||||
|
||||
const preloadedState = {};
|
||||
export const store = configureStore({
|
||||
middleware: [
|
||||
socketIoMiddleware(socketIOConnectionInstance),
|
||||
customSocketIOMiddleware,
|
||||
thunk,
|
||||
routerMiddleware,
|
||||
settingsApi.middleware,
|
||||
],
|
||||
reducer: rootReducer(createHashHistory()),
|
||||
preloadedState,
|
||||
});
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppThunk<ReturnType = void> = ThunkAction<
|
||||
ReturnType,
|
||||
RootState,
|
||||
unknown,
|
||||
Action<string>
|
||||
>;
|
||||
export const history = createReduxHistory(store);
|
||||
|
||||
Reference in New Issue
Block a user