diff --git a/README.md b/README.md index 8aca9f189..611077915 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,5 @@ components (no need to write them in the store file) ## Instructions - Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save. -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_redux-list-of-todos/) +- Replace `` with your Github username in the [DEMO LINK](https://dmitruz.github.io/react_redux-list-of-todos/) - Follow the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline) diff --git a/package-lock.json b/package-lock.json index 2be6d1de6..9bbd2f072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^1.9.12", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", @@ -1187,9 +1187,9 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.5.tgz", - "integrity": "sha512-mHRY2FkuoYCf5U0ahIukkaRo5LSZsxrTSgMJheFoyf3VXsTvfM9OfWcZIDIDB521kdPrScHHnRp+JRNjCfUO5A==", + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.9.12.tgz", + "integrity": "sha512-/OcmxMa34lYLFlGx7Ig926W1U1qjrnXbjFJ2TzUcDaLmED+A5se652NcWwGOidXRuMAOYLPU2jNYBEkKyXrFJA==", "dev": true, "dependencies": { "@octokit/rest": "^17.11.2", diff --git a/package.json b/package.json index 4f3d32089..7ad4f3696 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^1.9.12", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", diff --git a/src/App.tsx b/src/App.tsx index e10753461..d58433b8c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,59 @@ import 'bulma/css/bulma.css'; import '@fortawesome/fontawesome-free/css/all.css'; import { Loader, TodoFilter, TodoList, TodoModal } from './components'; +import { useEffect, useState } from 'react'; +import { getTodos } from './api'; +import { setTodos } from './features/todos'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from './app/store'; -export const App = () => ( - <> -
-
-
-

Todos:

+export const App = () => { + const dispatch = useDispatch(); + const currentTodo = useSelector((state: RootState) => state.currentTodo.todo); + const todos = useSelector((state: RootState) => state.todos.todos); -
- -
+ const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + const fetchTodos = async () => { + setIsLoading(true); + try { + const response = await getTodos(); + + dispatch(setTodos(response)); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error fetching todos:', error); + } finally { + setIsLoading(false); + } + }; + + fetchTodos(); + }, [dispatch]); + + const noTodos = !todos.length; + const modalIsVisible = !!currentTodo; + + return ( + <> +
+
+
+

Todos:

+ +
+ +
-
- - +
+ {isLoading ? : !noTodos ? : null} +
-
- - -); + {modalIsVisible && } + + ); +}; diff --git a/src/app/store.ts b/src/app/store.ts index 3a9b384be..df1e89176 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -1,6 +1,9 @@ import { combineSlices, configureStore } from '@reduxjs/toolkit'; +import { currentTodoSlice } from '../features/currentTodo'; +import { todosSlice } from '../features/todos'; +import { filterSlice } from '../features/filter'; -const rootReducer = combineSlices(); +const rootReducer = combineSlices(currentTodoSlice, todosSlice, filterSlice); export const store = configureStore({ reducer: rootReducer, diff --git a/src/components/TodoFilter/TodoFilter.tsx b/src/components/TodoFilter/TodoFilter.tsx index c1b574ee7..9a9ca8c0a 100644 --- a/src/components/TodoFilter/TodoFilter.tsx +++ b/src/components/TodoFilter/TodoFilter.tsx @@ -1,14 +1,30 @@ import React from 'react'; +import { FilterStatus, setQuery, setStatus } from '../../features/filter'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../app/store'; + export const TodoFilter: React.FC = () => { + const dispatch = useDispatch(); + const query = useSelector((state: RootState) => state.filter.query); + + const handleStatusChange = (value: React.ChangeEvent) => { + dispatch(setStatus(value.target.value as FilterStatus)); + }; + + const handleQueryChange = (value: React.ChangeEvent) => { + dispatch(setQuery(value.target.value)); + }; + + const resetQuery = () => { + dispatch(setQuery('')); + }; + return ( -
event.preventDefault()} - > +

- @@ -22,19 +38,24 @@ export const TodoFilter: React.FC = () => { type="text" className="input" placeholder="Search..." + value={query} + onChange={handleQueryChange} /> - - {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} -

+

+ {!currentTodo.completed ? ( + Planned + ) : ( + Done + )} + {' by '} + {currentUser.name} +

+
+
+ ) + )} ); }; diff --git a/src/features/currentTodo.ts b/src/features/currentTodo.ts index 9e69e2240..c7f2156a1 100644 --- a/src/features/currentTodo.ts +++ b/src/features/currentTodo.ts @@ -1,10 +1,20 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { Todo } from '../types/Todo'; -const initialState = null as Todo | null; +const initialState = { + todo: null as Todo | null, +}; export const currentTodoSlice = createSlice({ name: 'currentTodo', initialState, - reducers: {}, + reducers: { + setCurrentTodo: (state, action: PayloadAction) => { + return { ...state, todo: action.payload }; + }, + }, }); + +export default currentTodoSlice.reducer; + +export const { setCurrentTodo } = currentTodoSlice.actions; diff --git a/src/features/filter.ts b/src/features/filter.ts index d0eaf4640..2eb70e202 100644 --- a/src/features/filter.ts +++ b/src/features/filter.ts @@ -1,6 +1,13 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -const initialState = { +export type FilterStatus = 'all' | 'active' | 'completed'; + +interface State { + query: string; + status: FilterStatus; +} + +const initialState: State = { query: '', status: 'all', }; @@ -8,5 +15,16 @@ const initialState = { export const filterSlice = createSlice({ name: 'filter', initialState, - reducers: {}, + reducers: { + setStatus: (state, action: PayloadAction) => { + return { ...state, status: action.payload }; + }, + setQuery: (state, action: PayloadAction) => { + return { ...state, query: action.payload }; + }, + }, }); + +export default filterSlice.reducer; + +export const { setStatus, setQuery } = filterSlice.actions; diff --git a/src/features/todos.ts b/src/features/todos.ts index 4555dea8d..f99b56432 100644 --- a/src/features/todos.ts +++ b/src/features/todos.ts @@ -1,8 +1,24 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { Todo } from '../types/Todo'; +type StateType = { + todos: Todo[]; +}; + +export const initialState: StateType = { + todos: [], +}; + export const todosSlice = createSlice({ name: 'todos', - initialState: [] as Todo[], - reducers: {}, + initialState, + reducers: { + setTodos: (state, action: PayloadAction) => { + // eslint-disable-next-line no-param-reassign + state.todos = action.payload; + }, + }, }); + +export default todosSlice.reducer; +export const { setTodos } = todosSlice.actions;