From 487715a3e35301e2eda5ec94c4945b5f00bfb91c Mon Sep 17 00:00:00 2001
From: Renaud Chaput <renchap@gmail.com>
Date: Tue, 9 May 2023 16:56:26 +0200
Subject: [PATCH] [Glitch] Type Redux store and middleware

Port d67de22458e599447c0d5c85ecbd6fb5aef9b4f4 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 .../glitch/containers/compose_container.jsx   |  2 +-
 .../flavours/glitch/containers/mastodon.jsx   |  2 +-
 app/javascript/flavours/glitch/main.jsx       |  2 +-
 .../glitch/reducers/{index.js => index.ts}    |  4 ++-
 .../flavours/glitch/store/configureStore.js   | 16 ------------
 app/javascript/flavours/glitch/store/index.ts | 23 ++++++++++++++++
 .../errors.js => store/middlewares/errors.ts} |  7 ++---
 .../middlewares/loading_bar.ts}               | 12 ++++++---
 .../sounds.js => store/middlewares/sounds.ts} | 26 +++++++++++++------
 9 files changed, 60 insertions(+), 34 deletions(-)
 rename app/javascript/flavours/glitch/reducers/{index.js => index.ts} (97%)
 delete mode 100644 app/javascript/flavours/glitch/store/configureStore.js
 create mode 100644 app/javascript/flavours/glitch/store/index.ts
 rename app/javascript/flavours/glitch/{middleware/errors.js => store/middlewares/errors.ts} (66%)
 rename app/javascript/flavours/glitch/{middleware/loading_bar.js => store/middlewares/loading_bar.ts} (68%)
 rename app/javascript/flavours/glitch/{middleware/sounds.js => store/middlewares/sounds.ts} (54%)

diff --git a/app/javascript/flavours/glitch/containers/compose_container.jsx b/app/javascript/flavours/glitch/containers/compose_container.jsx
index dada4cfed7..9b84ed583d 100644
--- a/app/javascript/flavours/glitch/containers/compose_container.jsx
+++ b/app/javascript/flavours/glitch/containers/compose_container.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import { Provider } from 'react-redux';
 import PropTypes from 'prop-types';
-import { store } from 'flavours/glitch/store/configureStore';
+import { store } from 'flavours/glitch/store';
 import { hydrateStore } from 'flavours/glitch/actions/store';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from 'mastodon/locales';
diff --git a/app/javascript/flavours/glitch/containers/mastodon.jsx b/app/javascript/flavours/glitch/containers/mastodon.jsx
index aca7f4dc59..1044fdd160 100644
--- a/app/javascript/flavours/glitch/containers/mastodon.jsx
+++ b/app/javascript/flavours/glitch/containers/mastodon.jsx
@@ -5,7 +5,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
 import { Provider as ReduxProvider } from 'react-redux';
 import { BrowserRouter, Route } from 'react-router-dom';
 import { ScrollContext } from 'react-router-scroll-4';
-import { store } from 'flavours/glitch/store/configureStore';
+import { store } from 'flavours/glitch/store';
 import UI from 'flavours/glitch/features/ui';
 import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
 import { hydrateStore } from 'flavours/glitch/actions/store';
diff --git a/app/javascript/flavours/glitch/main.jsx b/app/javascript/flavours/glitch/main.jsx
index fa8ba47d49..1ac7d579ec 100644
--- a/app/javascript/flavours/glitch/main.jsx
+++ b/app/javascript/flavours/glitch/main.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications';
 import Mastodon from 'flavours/glitch/containers/mastodon';
-import { store } from 'flavours/glitch/store/configureStore';
+import { store } from 'flavours/glitch/store';
 import { me } from 'flavours/glitch/initial_state';
 import ready from 'flavours/glitch/ready';
 
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.ts
similarity index 97%
rename from app/javascript/flavours/glitch/reducers/index.js
rename to app/javascript/flavours/glitch/reducers/index.ts
index 5b7bdbf699..da2174fc2c 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.ts
@@ -91,4 +91,6 @@ const reducers = {
   followed_tags,
 };
 
-export default combineReducers(reducers);
+const rootReducer = combineReducers(reducers);
+
+export { rootReducer };
diff --git a/app/javascript/flavours/glitch/store/configureStore.js b/app/javascript/flavours/glitch/store/configureStore.js
deleted file mode 100644
index cb17dd9ce8..0000000000
--- a/app/javascript/flavours/glitch/store/configureStore.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { configureStore } from '@reduxjs/toolkit';
-import thunk from 'redux-thunk';
-import appReducer from '../reducers';
-import loadingBarMiddleware from '../middleware/loading_bar';
-import errorsMiddleware from '../middleware/errors';
-import soundsMiddleware from '../middleware/sounds';
-
-export const store = configureStore({
-  reducer: appReducer,
-  middleware: [
-    thunk,
-    loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
-    errorsMiddleware(),
-    soundsMiddleware(),
-  ],
-});
diff --git a/app/javascript/flavours/glitch/store/index.ts b/app/javascript/flavours/glitch/store/index.ts
new file mode 100644
index 0000000000..822c01aa90
--- /dev/null
+++ b/app/javascript/flavours/glitch/store/index.ts
@@ -0,0 +1,23 @@
+import { configureStore } from '@reduxjs/toolkit';
+import { rootReducer } from '../reducers';
+import { loadingBarMiddleware } from './middlewares/loading_bar';
+import { errorsMiddleware } from './middlewares/errors';
+import { soundsMiddleware } from './middlewares/sounds';
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+
+export const store = configureStore({
+  reducer: rootReducer,
+  middleware: getDefaultMiddleware =>
+    getDefaultMiddleware().concat(
+      loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }))
+      .concat(errorsMiddleware)
+      .concat(soundsMiddleware()),
+});
+
+// Infer the `RootState` and `AppDispatch` types from the store itself
+export type RootState = ReturnType<typeof rootReducer>
+// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
+export type AppDispatch = typeof store.dispatch
+
+export const useAppDispatch: () => AppDispatch = useDispatch;
+export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
diff --git a/app/javascript/flavours/glitch/middleware/errors.js b/app/javascript/flavours/glitch/store/middlewares/errors.ts
similarity index 66%
rename from app/javascript/flavours/glitch/middleware/errors.js
rename to app/javascript/flavours/glitch/store/middlewares/errors.ts
index 3639a59512..be9e586521 100644
--- a/app/javascript/flavours/glitch/middleware/errors.js
+++ b/app/javascript/flavours/glitch/store/middlewares/errors.ts
@@ -1,9 +1,11 @@
+import { Middleware } from 'redux';
 import { showAlertForError } from 'flavours/glitch/actions/alerts';
+import { RootState } from '..';
 
 const defaultFailSuffix = 'FAIL';
 
-export default function errorsMiddleware() {
-  return ({ dispatch }) => next => action => {
+export const errorsMiddleware: Middleware<Record<string, never>, RootState> =
+  ({ dispatch }) => next => action => {
     if (action.type && !action.skipAlert) {
       const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
 
@@ -14,4 +16,3 @@ export default function errorsMiddleware() {
 
     return next(action);
   };
-}
diff --git a/app/javascript/flavours/glitch/middleware/loading_bar.js b/app/javascript/flavours/glitch/store/middlewares/loading_bar.ts
similarity index 68%
rename from app/javascript/flavours/glitch/middleware/loading_bar.js
rename to app/javascript/flavours/glitch/store/middlewares/loading_bar.ts
index da8cc4c7d3..e860b31b6f 100644
--- a/app/javascript/flavours/glitch/middleware/loading_bar.js
+++ b/app/javascript/flavours/glitch/store/middlewares/loading_bar.ts
@@ -1,8 +1,14 @@
 import { showLoading, hideLoading } from 'react-redux-loading-bar';
+import { Middleware } from 'redux';
+import { RootState } from '..';
 
-const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED'];
+interface Config {
+  promiseTypeSuffixes?: string[]
+}
 
-export default function loadingBarMiddleware(config = {}) {
+const defaultTypeSuffixes: Config['promiseTypeSuffixes'] = ['PENDING', 'FULFILLED', 'REJECTED'];
+
+export  const loadingBarMiddleware = (config: Config = {}): Middleware<Record<string, never>, RootState> => {
   const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes;
 
   return ({ dispatch }) => next => (action) => {
@@ -22,4 +28,4 @@ export default function loadingBarMiddleware(config = {}) {
 
     return next(action);
   };
-}
+};
diff --git a/app/javascript/flavours/glitch/middleware/sounds.js b/app/javascript/flavours/glitch/store/middlewares/sounds.ts
similarity index 54%
rename from app/javascript/flavours/glitch/middleware/sounds.js
rename to app/javascript/flavours/glitch/store/middlewares/sounds.ts
index 7f23889836..c9d51f857f 100644
--- a/app/javascript/flavours/glitch/middleware/sounds.js
+++ b/app/javascript/flavours/glitch/store/middlewares/sounds.ts
@@ -1,4 +1,12 @@
-const createAudio = sources => {
+import { Middleware, AnyAction } from 'redux';
+import { RootState } from '..';
+
+interface AudioSource {
+  src: string
+  type: string
+}
+
+const createAudio = (sources: AudioSource[]) => {
   const audio = new Audio();
   sources.forEach(({ type, src }) => {
     const source = document.createElement('source');
@@ -9,7 +17,7 @@ const createAudio = sources => {
   return audio;
 };
 
-const play = audio => {
+const play = (audio: HTMLAudioElement) => {
   if (!audio.paused) {
     audio.pause();
     if (typeof audio.fastSeek === 'function') {
@@ -22,8 +30,8 @@ const play = audio => {
   audio.play();
 };
 
-export default function soundsMiddleware() {
-  const soundCache = {
+export  const soundsMiddleware = (): Middleware<Record<string, never>, RootState> => {
+  const soundCache: {[key: string]: HTMLAudioElement} = {
     boop: createAudio([
       {
         src: '/sounds/boop.ogg',
@@ -36,11 +44,13 @@ export default function soundsMiddleware() {
     ]),
   };
 
-  return () => next => action => {
-    if (action.meta && action.meta.sound && soundCache[action.meta.sound]) {
-      play(soundCache[action.meta.sound]);
+  return () => next => (action: AnyAction) => {
+    const sound = action?.meta?.sound;
+
+    if (sound && soundCache[sound]) {
+      play(soundCache[sound]);
     }
 
     return next(action);
   };
-}
+};