Skip to content

Commit

Permalink
Developed
Browse files Browse the repository at this point in the history
  • Loading branch information
NazariiAlieksieiev committed Oct 29, 2024
1 parent f1536f7 commit 8d79d1c
Show file tree
Hide file tree
Showing 21 changed files with 1,082 additions and 265 deletions.
6 changes: 3 additions & 3 deletions cypress/integration/page.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Cypress.on('fail', e => {
});

describe('', () => {
beforeEach(() => {
if (failed) Cypress.runner.stop();
});
// beforeEach(() => {
// if (failed) Cypress.runner.stop();
// });

describe('Page with no todos', () => {
beforeEach(() => {
Expand Down
257 changes: 158 additions & 99 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,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",
Expand Down
180 changes: 37 additions & 143 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,157 +1,51 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
import { TodoFilter } from './components/TodoFilter/TodoFilter';
import React, { useContext, useEffect } from 'react';
import { UserWarning } from './UserWarning';
import { getTodos, USER_ID } from './api/todos';
import { TodoList } from './components/TodoList/TodoList';
import { ErrorMessage } from './types/ErrorMessage';
import { TodoForm } from './components/TodoForm/TodoForm';
import { Notification } from './components/Notification/Notification';
import { DispatchContext, StateContext } from './Store';
import { onAutoCloseNotification } from './utils/autoCloseNotification';

export const App: React.FC = () => {
const dispatch = useContext(DispatchContext);
const { todos } = useContext(StateContext);

useEffect(() => {
dispatch({ type: 'download' });
getTodos()
.then(downloadedTodos => {
dispatch({ type: 'downloadSuccess', todos: downloadedTodos });
})
.catch(() => {
dispatch({ type: 'failure', errorMessage: ErrorMessage.load });
})
.finally(() => {
onAutoCloseNotification(dispatch);
});
}, []);

Check warning on line 30 in src/App.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array

if (!USER_ID) {
return <UserWarning />;
}

return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>

<div className="todoapp__content">
<header className="todoapp__header">
{/* this button should have `active` class only if all todos are completed */}
<button
type="button"
className="todoapp__toggle-all active"
data-cy="ToggleAllButton"
/>

{/* Add a todo on form submit */}
<form>
<input
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
/>
</form>
</header>

<section className="todoapp__main" data-cy="TodoList">
{/* This is a completed todo */}
<div data-cy="Todo" className="todo completed">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Completed Todo
</span>

{/* Remove button appears only on hover */}
<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* This todo is an active todo */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Not Completed Todo
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* This todo is being edited */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>
<TodoForm />

{/* This form is shown instead of the title and remove button */}
<form>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value="Todo is being edited now"
/>
</form>
</div>
<TodoList />

{/* This todo is in loadind state */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Todo is being saved now
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
</section>

{/* Hide the footer if there are no todos */}
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
3 items left
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
<a
href="#/"
className="filter__link selected"
data-cy="FilterLinkAll"
>
All
</a>

<a
href="#/active"
className="filter__link"
data-cy="FilterLinkActive"
>
Active
</a>

<a
href="#/completed"
className="filter__link"
data-cy="FilterLinkCompleted"
>
Completed
</a>
</nav>

{/* this button should be disabled if there are no completed todos */}
<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
>
Clear completed
</button>
</footer>
{todos.length > 0 && <TodoFilter />}
</div>

<Notification />
</div>
);
};
150 changes: 150 additions & 0 deletions src/Store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useReducer } from 'react';
import { ErrorMessage } from './types/ErrorMessage';
import { Todo } from './types/Todo';
import { Status } from './types/Status';
export type Action =
| { type: 'download' }
| { type: 'downloadSuccess'; todos: Todo[] }
| { type: 'failure'; errorMessage: ErrorMessage | null }
| { type: 'closeNotification' }
| { type: 'filterStatus'; status: Status }
| { type: 'addTodo'; tempTodo: Todo }
| { type: 'addingSuccess'; newTodo: Todo }
| { type: 'reset' }
| { type: 'startAction'; selectedTodo: number[] }
| { type: 'deletingSuccesses' }
| { type: 'editTodoSuccess'; index: number; updatedTodo: Todo }
| { type: 'toggleAllSuccesses'; completed: boolean };

interface RootState {
todos: Todo[];
tempTodo: Todo | null;
selectedTodo: number[];
status: Status;
isProcessing: boolean;
errorMessage: ErrorMessage | null;
}

const initialState: RootState = {
todos: [],
tempTodo: null,
selectedTodo: [],
status: Status.all,
isProcessing: false,
errorMessage: null,
};

const reducer = (state: RootState, action: Action): RootState => {
switch (action.type) {
case 'download':
return {
...state,
isProcessing: true,
errorMessage: null,
};
case 'downloadSuccess':
return {
...state,
todos: action.todos,
isProcessing: false,
};
case 'failure':
return {
...state,
errorMessage: action.errorMessage,
isProcessing: false,
};
case 'closeNotification':
return {
...state,
errorMessage: null,
};
case 'filterStatus':
return {
...state,
status: action.status,
};
case 'addTodo':
return {
...state,
tempTodo: action.tempTodo,
isProcessing: true,
errorMessage: null,
};
case 'addingSuccess':
return {
...state,
isProcessing: false,
tempTodo: null,
todos: [...state.todos, action.newTodo],
};
case 'reset':
return {
...state,
tempTodo: null,
selectedTodo: [],
};
case 'startAction':
return {
...state,
isProcessing: true,
errorMessage: null,
selectedTodo: action.selectedTodo,
};
case 'deletingSuccesses':
const newTodos = state.todos.filter(
todo => !state.selectedTodo.includes(todo.id),
);

return {
...state,
todos: newTodos,
isProcessing: false,
};
case 'editTodoSuccess': {
const updatedTodos = state.todos.map((todo, idx) =>
idx === action.index ? action.updatedTodo : todo,
);

return {
...state,
todos: updatedTodos,
isProcessing: false,
};
}

case 'toggleAllSuccesses': {
const updatedTodos = state.todos.map(todo => ({
...todo,
completed: action.completed,
}));

return {
...state,
todos: updatedTodos,
isProcessing: false,
};
}

default:
return state;
}
};

export const StateContext = React.createContext(initialState);
// eslint-disable-next-line @typescript-eslint/no-unused-vars, prettier/prettier
export const DispatchContext = React.createContext((action: Action) => { });

type Props = {
children: React.ReactNode;
};

export const GlobalStateProvider: React.FC<Props> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
};
15 changes: 15 additions & 0 deletions src/UserWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

export const UserWarning: React.FC = () => (
<section className="section">
<p className="box is-size-3">
Please get your <b> userId </b>{' '}
<a href="https://mate-academy.github.io/react_student-registration">
here
</a>{' '}
and save it in the app <pre>const USER_ID = ...</pre>
All requests to the API must be sent with this
<b> userId.</b>
</p>
</section>
);
Loading

0 comments on commit 8d79d1c

Please sign in to comment.