diff --git a/backend/db.sqlite3 b/backend/db.sqlite3
index 6265512..dca19f7 100644
Binary files a/backend/db.sqlite3 and b/backend/db.sqlite3 differ
diff --git a/package.json b/package.json
index c583f14..6bc9fee 100644
--- a/package.json
+++ b/package.json
@@ -1,45 +1,50 @@
{
- "name": "swpp-p3-react-tutorial",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@testing-library/jest-dom": "5.16.5",
- "@testing-library/react": "13.3.0",
- "@testing-library/user-event": "13.5.0",
- "@types/jest": "27.5.2",
- "@types/node": "16.11.56",
- "@types/react": "18.0.17",
- "@types/react-dom": "18.0.6",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-router": "6.3.0",
- "react-router-dom": "6.3.0",
- "react-scripts": "5.0.1",
- "typescript": "4.7.4",
- "web-vitals": "2.1.4"
- },
- "scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
- "eject": "react-scripts eject"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- }
+ "name": "swpp-p3-react-tutorial",
+ "version": "0.1.0",
+ "private": false,
+ "proxy": "http://127.0.0.1:8000",
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.8.5",
+ "@testing-library/jest-dom": "5.16.5",
+ "@testing-library/react": "13.3.0",
+ "@testing-library/user-event": "13.5.0",
+ "@types/jest": "27.5.2",
+ "@types/node": "16.11.56",
+ "@types/react": "18.0.17",
+ "@types/react-dom": "18.0.6",
+ "axios": "^0.27.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-redux": "^8.0.4",
+ "react-router": "6.3.0",
+ "react-router-dom": "6.3.0",
+ "react-scripts": "5.0.1",
+ "redux": "^4.2.0",
+ "typescript": "4.7.4",
+ "web-vitals": "2.1.4"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
}
diff --git a/redux-basics.js b/redux-basics.js
new file mode 100644
index 0000000..c37baf8
--- /dev/null
+++ b/redux-basics.js
@@ -0,0 +1,21 @@
+const { configureStore } = require("@reduxjs/toolkit"); // load module in Node.js
+const initialState = { number: 0 }; // default state
+// create identity reducer
+const reducer = (state = initialState, action) => {
+ if (action.type === "ADD") {
+ return { ...state, number: state.number + 1 };
+ } else if (action.type === "ADD_VALUE") {
+ return { ...state, number: state.number + action.value };
+ }
+ return state;
+};
+
+// create redux store
+const store = configureStore({ reducer: reducer });
+
+store.subscribe(() => {
+ console.log("[Subscription]", store.getState());
+});
+store.dispatch({ type: "ADD" });
+store.dispatch({ type: "ADD_VALUE", value: 5 });
+console.log(store.getState());
diff --git a/src/App.tsx b/src/App.tsx
index 4a1c771..0d2971c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,19 +5,25 @@ import NewTodo from "./containers/TodoList/NewTodo/NewTodo";
import TodoDetail from "./components/TodoDetail/TodoDetail";
function App() {
- return (
-
-
-
- } />
- } />
- } />
- } />
- Not Found} />
-
-
-
- );
+ return (
+
+
+
+ }
+ />
+ } />
+ } />
+ }
+ />
+ Not Found} />
+
+
+
+ );
}
export default App;
diff --git a/src/components/Todo/Todo.tsx b/src/components/Todo/Todo.tsx
index c207bad..156991f 100644
--- a/src/components/Todo/Todo.tsx
+++ b/src/components/Todo/Todo.tsx
@@ -1,19 +1,28 @@
import "./Todo.css";
interface IProps {
- title: string;
- clicked?: React.MouseEventHandler; // Defined by React
- done: boolean;
+ title: string;
+ clickDetail?: React.MouseEventHandler; // Defined by React
+ clickDone?: () => void;
+ clickDelete?: () => void;
+ done: boolean;
}
const Todo = (props: IProps) => {
- return (
-
-
- {props.title}
-
- {props.done &&
✓
}
-
- );
+ return (
+
+
+ {props.title}
+
+ {props.done &&
✓
}
+
+
+
+ );
};
export default Todo;
diff --git a/src/components/TodoDetail/TodoDetail.tsx b/src/components/TodoDetail/TodoDetail.tsx
index dd6fc30..a8da688 100644
--- a/src/components/TodoDetail/TodoDetail.tsx
+++ b/src/components/TodoDetail/TodoDetail.tsx
@@ -1,22 +1,30 @@
+import { useEffect } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { useParams } from "react-router";
+import { AppDispatch } from "../../store";
+import { selectTodo, fetchTodo } from "../../store/slices/todo";
import "./TodoDetail.css";
-type Props = {
- title: string;
- content: string;
-};
+const TodoDetail = () => {
+ const { id } = useParams();
+ const dispatch = useDispatch();
+ const todoState = useSelector(selectTodo);
+ useEffect(() => {
+ dispatch(fetchTodo(Number(id)));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [id]);
-const TodoDetail = (props: Props) => {
- return (
-
-
-
Name:
-
{props.title}
-
-
-
Content:
-
{props.content}
-
-
- );
+ return (
+
+
+
Name:
+
{todoState.selectedTodo?.title}
+
+
+
Content:
+
{todoState.selectedTodo?.content}
+
+
+ );
};
export default TodoDetail;
diff --git a/src/containers/TodoList/NewTodo/NewTodo.tsx b/src/containers/TodoList/NewTodo/NewTodo.tsx
index c23a5fb..8c21195 100644
--- a/src/containers/TodoList/NewTodo/NewTodo.tsx
+++ b/src/containers/TodoList/NewTodo/NewTodo.tsx
@@ -1,47 +1,55 @@
import { useState } from "react";
import { Navigate } from "react-router-dom";
+import { useDispatch } from "react-redux";
+import { AppDispatch } from "../../../store";
+import { postTodo } from "../../../store/slices/todo";
// import { useNavigate } from "react-router-dom";
import "./NewTodo.css";
export default function NewTodo() {
- const [title, setTitle] = useState("");
- const [content, setContent] = useState("");
- const [submitted, setSubmitted] = useState(false);
+ const [title, setTitle] = useState("");
+ const [content, setContent] = useState("");
+ const [submitted, setSubmitted] = useState(false);
+ const dispatch = useDispatch();
- // const navigate = useNavigate()
- // const postTodoHandler = () => {
- // const data = { title: title, content: content };
- // alert("Submitted\n" + data.title + "\n" + data.content);
- // setSubmitted(true);
- // navigate('/todos')
- // };
+ // const navigate = useNavigate()
+ // const postTodoHandler = () => {
+ // const data = { title: title, content: content };
+ // alert("Submitted\n" + data.title + "\n" + data.content);
+ // setSubmitted(true);
+ // navigate('/todos')
+ // };
- const postTodoHandler = () => {
- const data = { title: title, content: content };
- alert("Submitted\n" + data.title + "\n" + data.content);
- setSubmitted(true);
- };
+ const postTodoHandler = async () => {
+ const data = { title: title, content: content };
+ const result = await dispatch(postTodo(data));
+ if (result.payload) {
+ setSubmitted(true);
+ } else {
+ alert("Error on post Todo");
+ }
+ };
- if (submitted) {
- return ;
- } else {
- return (
-
-
Add a Todo
-
- setTitle(event.target.value)}
- />
-
-
- );
- }
+ if (submitted) {
+ return ;
+ } else {
+ return (
+
+
Add a Todo
+
+ setTitle(event.target.value)}
+ />
+
+
+ );
+ }
}
diff --git a/src/containers/TodoList/TodoList.tsx b/src/containers/TodoList/TodoList.tsx
index fa0fe61..f652e95 100644
--- a/src/containers/TodoList/TodoList.tsx
+++ b/src/containers/TodoList/TodoList.tsx
@@ -1,56 +1,56 @@
-import { useMemo, useState } from "react";
-import { NavLink } from "react-router-dom";
+import { useMemo, useState, useEffect } from "react";
+import { NavLink, useNavigate } from "react-router-dom";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ fetchTodos,
+ selectTodo,
+ deleteTodo,
+ toggleDone,
+} from "../../store/slices/todo";
import Todo from "../../components/Todo/Todo";
-import TodoDetail from "../../components/TodoDetail/TodoDetail";
import "./TodoList.css";
+import { AppDispatch } from "../../store";
interface IProps {
- title: string;
+ title: string;
}
type TodoType = { id: number; title: string; content: string; done: boolean };
export default function TodoList(props: IProps) {
- const { title } = props;
- const [selectedTodo, setSelectedTodo] = useState(null);
+ const navigate = useNavigate();
+ const { title } = props;
- const [todos, setTodos] = useState([
- { id: 1, title: "SWPP", content: "take swpp class", done: true },
- { id: 2, title: "Movie", content: "watch movie", done: false },
- { id: 3, title: "Dinner", content: "eat dinner", done: false },
- ]);
+ const todoState = useSelector(selectTodo);
+ const dispatch = useDispatch();
- const clickTodoHandler = (td: TodoType) => {
- if (selectedTodo === td) {
- setSelectedTodo(null);
- } else {
- setSelectedTodo(td);
- }
- };
+ useEffect(() => {
+ dispatch(fetchTodos());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
- const todoDetail = useMemo(() => {
- return selectedTodo ? (
-
- ) : null;
- }, [selectedTodo]);
+ const clickTodoHandler = (td: TodoType) => {
+ navigate("/todos/" + td.id);
+ };
- return (
-
-
{title}
-
- {todos.map((td) => {
- return (
- clickTodoHandler(td)}
- />
- );
- })}
- {todoDetail}
- New Todo
-
-
- );
+ return (
+
+
{title}
+
+ {todoState.todos.map((td) => {
+ return (
+ clickTodoHandler(td)}
+ clickDone={() => dispatch(toggleDone(td.id))}
+ clickDelete={() => dispatch(deleteTodo(td.id))}
+ />
+ );
+ })}
+ New Todo
+
+
+ );
}
diff --git a/src/index.tsx b/src/index.tsx
index 1fd12b7..da6371e 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,13 +1,17 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "./index.css";
+import App from "./App";
+import { Provider } from "react-redux";
+import { store } from "./store";
const root = ReactDOM.createRoot(
- document.getElementById('root') as HTMLElement
+ document.getElementById("root") as HTMLElement
);
root.render(
-
-
-
+
+
+
+
+
);
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 0000000..3a36d8f
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1,9 @@
+import { configureStore } from "@reduxjs/toolkit";
+import todoReducer from "./slices/todo";
+export const store = configureStore({
+ reducer: {
+ todo: todoReducer,
+ },
+});
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
diff --git a/src/store/slices/todo.ts b/src/store/slices/todo.ts
new file mode 100644
index 0000000..caa2acb
--- /dev/null
+++ b/src/store/slices/todo.ts
@@ -0,0 +1,113 @@
+import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
+import axios from "axios";
+import { RootState } from "..";
+
+export interface TodoType {
+ id: number;
+ title: string;
+ content: string;
+ done: boolean;
+}
+export interface TodoState {
+ todos: TodoType[];
+ selectedTodo: TodoType | null;
+}
+const initialState: TodoState = {
+ todos: [],
+ selectedTodo: null,
+};
+
+export const fetchTodos = createAsyncThunk("todo/fetchTodos", async () => {
+ const response = await axios.get("/api/todo/");
+ return response.data;
+});
+
+export const postTodo = createAsyncThunk(
+ "todo/postTodo",
+ async (td: Pick, { dispatch }) => {
+ const response = await axios.post("/api/todo/", td);
+ dispatch(todoActions.addTodo(response.data));
+ }
+);
+
+export const deleteTodo = createAsyncThunk(
+ "todo/deleteTodo",
+ async (id: TodoType["id"], { dispatch }) => {
+ await axios.delete(`/api/todo/${id}/`);
+ dispatch(todoActions.deleteTodo({ targetId: id }));
+ }
+);
+
+export const toggleDone = createAsyncThunk(
+ "todo/toggleDone",
+ async (id: TodoType["id"], { dispatch }) => {
+ await axios.put(`/api/todo/${id}/`);
+ dispatch(todoActions.toggleDone({ targetId: id }));
+ }
+);
+
+export const fetchTodo = createAsyncThunk(
+ "todo/fetchTodo",
+ async (id: TodoType["id"], { dispatch }) => {
+ const response = await axios.get(`/api/todo/${id}/`);
+ return response.data ?? null;
+ }
+);
+
+export const todoSlice = createSlice({
+ name: "todo",
+ initialState,
+ reducers: {
+ getAll: (state, action: PayloadAction<{ todos: TodoType[] }>) => {},
+ getTodo: (state, action: PayloadAction<{ targetId: number }>) => {
+ const target = state.todos.find(
+ (td) => td.id === action.payload.targetId
+ );
+ state.selectedTodo = target ?? null;
+ },
+ toggleDone: (state, action: PayloadAction<{ targetId: number }>) => {
+ const todo = state.todos.find(
+ (value) => value.id === action.payload.targetId
+ );
+ if (todo) {
+ todo.done = !todo.done;
+ }
+ },
+ deleteTodo: (state, action: PayloadAction<{ targetId: number }>) => {
+ const deleted = state.todos.filter((todo) => {
+ return todo.id !== action.payload.targetId;
+ });
+ state.todos = deleted;
+ },
+ addTodo: (
+ state,
+ action: PayloadAction<{ title: string; content: string }>
+ ) => {
+ const newTodo = {
+ id: state.todos[state.todos.length - 1].id + 1, // temporary
+ title: action.payload.title,
+ content: action.payload.content,
+ done: false,
+ };
+ state.todos.push(newTodo);
+ },
+ },
+ extraReducers: (builder) => {
+ // Add reducers for additional action types here, and handle loading state as needed
+ builder.addCase(fetchTodos.fulfilled, (state, action) => {
+ // Add user to the state array
+ state.todos = action.payload;
+ });
+ builder.addCase(fetchTodo.fulfilled, (state, action) => {
+ state.selectedTodo = action.payload;
+ });
+ builder.addCase(postTodo.rejected, (_state, action) => {
+ console.error(action.error);
+ });
+ },
+});
+
+export const todoActions = todoSlice.actions;
+export const selectTodo = (state: RootState) => state.todo;
+
+export default todoSlice.reducer;
diff --git a/yarn.lock b/yarn.lock
index a3d83b1..7f23b87 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1042,6 +1042,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.12.1":
+ version "7.19.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
+ integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.3.3":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
@@ -1553,6 +1560,16 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
+"@reduxjs/toolkit@^1.8.5":
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.5.tgz#c14bece03ee08be88467f22dc0ecf9cf875527cd"
+ integrity sha512-f4D5EXO7A7Xq35T0zRbWq5kJQyXzzscnHKmjnu2+37B3rwHU6mX9PYlbfXdnxcY6P/7zfmjhgan0Z+yuOfeBmA==
+ dependencies:
+ immer "^9.0.7"
+ redux "^4.1.2"
+ redux-thunk "^2.4.1"
+ reselect "^4.1.5"
+
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -1907,6 +1924,14 @@
dependencies:
"@types/node" "*"
+"@types/hoist-non-react-statics@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
@@ -2081,6 +2106,11 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
+"@types/use-sync-external-store@^0.0.3":
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
+ integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
+
"@types/ws@^8.5.1":
version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
@@ -2617,6 +2647,14 @@ axe-core@^4.4.3:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
+axios@^0.27.2:
+ version "0.27.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
+ integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
+ dependencies:
+ follow-redirects "^1.14.9"
+ form-data "^4.0.0"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -4380,6 +4418,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
+follow-redirects@^1.14.9:
+ version "1.15.2"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+ integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+
fork-ts-checker-webpack-plugin@^6.5.0:
version "6.5.2"
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340"
@@ -4408,6 +4451,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -4675,6 +4727,13 @@ history@^5.2.0:
dependencies:
"@babel/runtime" "^7.7.6"
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hoopy@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -7253,7 +7312,7 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
-react-is@^16.13.1:
+react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7268,6 +7327,18 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
+react-redux@^8.0.4:
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.4.tgz#80c31dffa8af9526967c4267022ae1525ff0e36a"
+ integrity sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ "@types/hoist-non-react-statics" "^3.3.1"
+ "@types/use-sync-external-store" "^0.0.3"
+ hoist-non-react-statics "^3.3.2"
+ react-is "^18.0.0"
+ use-sync-external-store "^1.0.0"
+
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
@@ -7401,6 +7472,18 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redux-thunk@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
+ integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
+
+redux@^4.1.2, redux@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
+ integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
@@ -7499,6 +7582,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+reselect@^4.1.5:
+ version "4.1.6"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656"
+ integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==
+
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -8475,6 +8563,11 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
+use-sync-external-store@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+ integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"