diff --git a/package.json b/package.json index 45a132c..9b07795 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/client/actions/settings.actions.tsx b/src/client/actions/settings.actions.tsx index 0a9fedd..94a6d92 100644 --- a/src/client/actions/settings.actions.tsx +++ b/src/client/actions/settings.actions.tsx @@ -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, }); }; diff --git a/src/client/components/App.tsx b/src/client/components/App.tsx index bb02ff7..529c59a 100644 --- a/src/client/components/App.tsx +++ b/src/client/components/App.tsx @@ -119,7 +119,6 @@ export const App = (): ReactElement => {
- } /> } /> diff --git a/src/client/components/Dashboard/Dashboard.tsx b/src/client/components/Dashboard/Dashboard.tsx index edb40b5..601f966 100644 --- a/src/client/components/Dashboard/Dashboard.tsx +++ b/src/client/components/Dashboard/Dashboard.tsx @@ -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 (

Dashboard

- - {!isEmpty(recentComics) ? ( - <> - {/* Pull List */} - - <> -
-
-
- - - - - - - - - - - - - - - - -
- Pos - Team - Pld -
138
- - - {/* Stats */} - {!isEmpty(libraryStatistics) && ( - - )} - {/* Wanted comics */} - {!isEmpty(wantedComics) && ( - - )} - {/* Recent imports */} - - - {/* Volumes */} - {!isEmpty(volumeGroups) && ( - - )} - - ) : ( - - )}
); diff --git a/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx b/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx index 9b4290b..63e267b 100644 --- a/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx +++ b/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx @@ -34,7 +34,11 @@ export const AirDCPPSettingsForm = (): ReactElement => { return ( <> - + {!isEmpty(airDCPPSettings.airDCPPState.socketConnectionInformation) ? ( { - 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 ( - <> - -
 {torrents && JSON.stringify(torrents, null, 4)} 
- - ); + return <>; }; export default QbittorrentConnectionForm; diff --git a/src/client/components/shared/ConnectionForm/ConnectionForm.tsx b/src/client/components/shared/ConnectionForm/ConnectionForm.tsx index 6b5db17..5de58bc 100644 --- a/src/client/components/shared/ConnectionForm/ConnectionForm.tsx +++ b/src/client/components/shared/ConnectionForm/ConnectionForm.tsx @@ -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 }) => (
-

Configure AirDC++

- +

{formHeading}

+

diff --git a/src/client/hooks/store.ts b/src/client/hooks/store.ts new file mode 100644 index 0000000..90515cb --- /dev/null +++ b/src/client/hooks/store.ts @@ -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 = useSelector; diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js index 3c2396e..897e6f0 100644 --- a/src/client/reducers/index.js +++ b/src/client/reducers/index.js @@ -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, }; diff --git a/src/client/reducers/settings.reducer.ts b/src/client/reducers/settings.reducer.ts index ebedbc5..c04ceb0 100644 --- a/src/client/reducers/settings.reducer.ts +++ b/src/client/reducers/settings.reducer.ts @@ -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; +} +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; diff --git a/src/client/services/acquisition.api.ts b/src/client/services/acquisition.api.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/client/services/empty.api.ts b/src/client/services/empty.api.ts new file mode 100644 index 0000000..fd72cfd --- /dev/null +++ b/src/client/services/empty.api.ts @@ -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: () => ({}), +}); diff --git a/src/client/services/settings.api.ts b/src/client/services/settings.api.ts new file mode 100644 index 0000000..bce1989 --- /dev/null +++ b/src/client/services/settings.api.ts @@ -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; diff --git a/src/client/services/torrents.api.ts b/src/client/services/torrents.api.ts new file mode 100644 index 0000000..ee496ef --- /dev/null +++ b/src/client/services/torrents.api.ts @@ -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; diff --git a/src/client/store/index.ts b/src/client/store/index.ts index c6de8da..c7dc458 100644 --- a/src/client/store/index.ts +++ b/src/client/store/index.ts @@ -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; +export type AppThunk = ThunkAction< + ReturnType, + RootState, + unknown, + Action +>; export const history = createReduxHistory(store);